feat: add support for pointer cursor (#517)

* feat: add support for pointer cursor

(cherry picked from commit dcabf91d843f2487ad32ceacfdd2d1b9d7c2a5bf)

* fix: correct useEffect update

(cherry picked from commit cbf0a821cd5a9b72b14b63c317d88e68fbd6783d)

* feat: remove default values

* feat: pointer cursor

Signed-off-by: Innei <i@innei.in>

* fix: remove select content animation when position is `item-aligned`

Signed-off-by: Innei <i@innei.in>

* update hover

Signed-off-by: Innei <i@innei.in>

---------

Signed-off-by: Innei <i@innei.in>
Co-authored-by: Innei <i@innei.in>
This commit is contained in:
张宇 2024-09-21 16:59:19 +08:00 committed by GitHub
parent 4dadd3119a
commit b6ff6fb48f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 97 additions and 49 deletions

View File

@ -19,6 +19,7 @@ export const createDefaultSettings = (): UISettings => ({
modalDraggable: true,
modalOpaque: true,
reduceMotion: false,
usePointerCursor: false,
// Font
uiFontFamily: "SN Pro",

View File

@ -103,6 +103,7 @@ export const ShadowDOM: FC<PropsWithChildren<React.HTMLProps<HTMLElement>>> & {
const uiFont = useUISettingKey("uiFontFamily")
const reduceMotion = useReduceMotion()
const usePointerCursor = useUISettingKey("usePointerCursor")
return (
<root.div {...rest}>
@ -111,8 +112,9 @@ export const ShadowDOM: FC<PropsWithChildren<React.HTMLProps<HTMLElement>>> & {
style={useMemo(
() => ({
fontFamily: `${uiFont},"SN Pro", system-ui, sans-serif`,
"--pointer": usePointerCursor ? "pointer" : "default",
}),
[uiFont],
[uiFont, usePointerCursor],
)}
id="shadow-html"
data-motion-reduce={reduceMotion}

View File

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

View File

@ -24,7 +24,7 @@ const ContextMenuSubTrigger = React.forwardRef<
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-theme-item-hover focus:text-theme-foreground data-[state=open]:bg-theme-item-hover data-[state=open]:text-theme-foreground",
"flex cursor-menu select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-theme-item-hover focus:text-theme-foreground data-[state=open]:bg-theme-item-hover data-[state=open]:text-theme-foreground",
inset && "pl-8",
"center gap-2",
className,
@ -81,7 +81,7 @@ const ContextMenuItem = React.forwardRef<
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-theme-item-hover focus:text-theme-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-menu select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-theme-item-hover focus:text-theme-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"focus-within:outline-transparent data-[highlighted]:bg-theme-item-hover dark:data-[highlighted]:bg-neutral-800",
inset && "pl-8",
className,
@ -98,7 +98,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-theme-item-hover focus:text-theme-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-checkbox select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-theme-item-hover focus:text-theme-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"focus-within:outline-transparent",
className,
)}

View File

@ -24,7 +24,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-theme-item-hover data-[state=open]:bg-theme-item-active",
"flex cursor-menu select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-theme-item-hover data-[state=open]:bg-theme-item-active",
inset && "pl-8",
className,
)}
@ -80,7 +80,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-theme-item-hover data-[disabled]:pointer-events-none data-[state=open]:bg-theme-item-active data-[disabled]:opacity-50",
"relative flex cursor-menu select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-theme-item-hover data-[disabled]:pointer-events-none data-[state=open]:bg-theme-item-active data-[disabled]:opacity-50",
inset && "pl-8",
"focus-within:!outline-transparent",
className,
@ -104,7 +104,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-theme-item-hover data-[disabled]:pointer-events-none data-[state=open]:bg-theme-item-active data-[disabled]:opacity-50",
"relative flex cursor-menu select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-theme-item-hover data-[disabled]:pointer-events-none data-[state=open]:bg-theme-item-active data-[disabled]:opacity-50",
className,
)}
checked={checked}

View File

@ -108,7 +108,7 @@ const MediaImpl: FC<MediaProps> = ({
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<MediaProps> = ({
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}

View File

@ -59,7 +59,7 @@ export function SwipeMedia({
<div
ref={hoverRef}
className={cn(
"relative flex w-full items-center overflow-hidden",
"relative flex w-full items-center overflow-hidden ",
styles["swipe-wrapper"],
className,
)}

View File

@ -22,6 +22,7 @@ const SelectTrigger = React.forwardRef<
"ring-accent/20 duration-200 focus:border-accent/80 focus:outline-none focus:ring-2",
"border border-border",
size === "sm" ? "h-7 p-2 text-sm" : "h-9 px-3 py-2 text-sm",
"hover:border-accent/80",
className,
)}
{...props}
@ -40,7 +41,7 @@ const SelectScrollUpButton = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
className={cn("flex cursor-select items-center justify-center py-1", className)}
{...props}
>
<i className="i-mingcute-up-line" />
@ -54,7 +55,7 @@ const SelectScrollDownButton = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
className={cn("flex cursor-select items-center justify-center py-1", className)}
{...props}
>
<i className="i-mingcute-down-line" />
@ -70,9 +71,11 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
className={cn(
"shadow-perfect relative z-[1000] max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground 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",
position === "popper" &&
"shadow-perfect relative z-[1000] max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground",
position === "popper" && [
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
"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,
)}
position={position}
@ -113,7 +116,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
"pointer-events-auto relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-theme-item-active focus:text-theme-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"pointer-events-auto relative flex w-full cursor-select items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-theme-item-active focus:text-theme-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}

View File

@ -9,14 +9,17 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
"peer inline-flex h-5 w-9 shrink-0 cursor-switch items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
"duration-200 data-[state=checked]:bg-accent data-[state=unchecked]:bg-theme-inactive dark:data-[state=unchecked]:bg-theme-accent-100/10",
"hover:data-[state=unchecked]:bg-theme-inactive/80 dark:hover:data-[state=unchecked]:bg-theme-accent-100/15",
"hover:data-[state=checked]:bg-accent/80 dark:hover:data-[state=checked]:bg-accent",
"group",
className,
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb className="pointer-events-none block size-4 rounded-full bg-background ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0" />
<SwitchPrimitives.Thumb className="pointer-events-none block size-4 rounded-full bg-background ring-0 transition-transform duration-200 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0 group-hover:dark:data-[state=checked]:!bg-black/80" />
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName

View File

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

View File

@ -65,7 +65,7 @@ export const DailyReportTitle = ({
<div className="font-medium">{t("ai_daily.title", { title })}</div>
<Tooltip>
<TooltipTrigger asChild>
<i className="i-mgc-question-cute-re translate-y-px text-sm" />
<i className="i-mgc-question-cute-re text-sm" />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>

View File

@ -25,20 +25,21 @@ export const RecommendationCard: FC<RecommendationCardProps> = memo(({ data, rou
<CardContent className="p-5 pt-0">
<ul className="space-y-1 text-sm text-muted-foreground">
{Object.keys(data.routes).map((route) => (
<li
key={route}
className="duration-200 hover:text-theme-foreground-hover"
onClick={() => {
present({
content: () => (
<RecommendationContent routePrefix={routePrefix} route={data.routes[route]} />
),
icon: <FeedIcon className="size-4" size={16} siteUrl={`https://${data.url}`} />,
title: `${data.name} - ${data.routes[route].name}`,
})
}}
>
{data.routes[route].name}
<li key={route} className="duration-200 hover:text-theme-foreground-hover">
<button
type="button"
onClick={() => {
present({
content: () => (
<RecommendationContent routePrefix={routePrefix} route={data.routes[route]} />
),
icon: <FeedIcon className="size-4" size={16} siteUrl={`https://${data.url}`} />,
title: `${data.name} - ${data.routes[route].name}`,
})
}}
>
{data.routes[route].name}
</button>
</li>
))}
</ul>

View File

@ -58,7 +58,7 @@ export function VideoItem({ entryId, entryPreview, translation }: UniversalItemP
return (
<GridItem entryId={entryId} entryPreview={entryPreview} translation={translation}>
<div
className="w-full"
className="w-full cursor-card"
onClick={() => {
if (iframeSrc) {
modalStack.present({

View File

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

View File

@ -219,7 +219,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl
<a
href={populatedFullHref || void 0}
target="_blank"
className="-mx-6 block cursor-default rounded-lg p-6 transition-colors hover:bg-theme-item-hover focus-visible:bg-theme-item-hover focus-visible:!outline-none @sm:-mx-3 @sm:p-3"
className="-mx-6 block cursor-button rounded-lg p-6 transition-colors hover:bg-theme-item-hover focus-visible:bg-theme-item-hover focus-visible:!outline-none @sm:-mx-3 @sm:p-3"
rel="noreferrer"
>
<div className="select-text break-words text-3xl font-bold">

View File

@ -120,7 +120,7 @@ function FeedCategoryImpl({
{!!showCollapse && (
<div
className={cn(
"flex w-full items-center justify-between rounded-md px-2.5 transition-colors",
"flex w-full cursor-menu items-center justify-between rounded-md px-2.5 transition-colors",
(isActive || isContextMenuOpen) && "bg-native-active",
)}
onClick={(e) => {

View File

@ -126,7 +126,7 @@ const LogoContextMenu: FC<PropsWithChildren> = ({ 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",
)}

View File

@ -68,7 +68,7 @@ const FeedItemImpl = ({ view, feedId, className, showUnreadCount = true }: FeedI
<div
data-feed-id={feedId}
className={cn(
"flex w-full items-center justify-between rounded-md py-[2px] pr-2.5 text-sm font-medium leading-loose",
"flex w-full cursor-menu items-center justify-between rounded-md py-[2px] pr-2.5 text-sm font-medium leading-loose",
(isActive || isContextMenuOpen) && "bg-native-active",
className,

View File

@ -117,7 +117,7 @@ function FeedListImpl({ className, view }: { className?: string; view: number })
<ScrollArea.ScrollArea mask={false} flex viewportClassName="!px-3" rootClassName="h-full">
<div
className={cn(
"flex h-8 w-full shrink-0 items-center rounded-md px-2.5 transition-colors",
"flex h-8 w-full shrink-0 cursor-menu items-center rounded-md px-2.5 transition-colors",
feedId === FEED_COLLECTION_LIST && "bg-native-active",
)}
onClick={(e) => {
@ -140,7 +140,7 @@ function FeedListImpl({ className, view }: { className?: string; view: number })
<div className="flex h-full flex-1 items-center font-normal text-zinc-500">
<Link
to="/discover"
className="absolute inset-0 mt-[-3.75rem] flex h-full flex-1 cursor-default flex-col items-center justify-center gap-2"
className="absolute inset-0 mt-[-3.75rem] flex h-full flex-1 cursor-menu flex-col items-center justify-center gap-2"
onClick={stopPropagation}
>
<i className="i-mgc-add-cute-re text-3xl" />

View File

@ -373,7 +373,7 @@ const SubscriptionItem: FC<{
data-feed-id={subscription.feedId}
>
<a
className="flex flex-1 cursor-default items-center"
className="flex flex-1 cursor-menu items-center"
href={subscription.feeds.siteUrl!}
target="_blank"
>

View File

@ -37,7 +37,7 @@ export const SettingSwitch: Component<{
return (
<div className={cn("mb-3 flex items-center justify-between gap-4", className)}>
<Label htmlFor={id}>{label}</Label>
<Switch id={id} checked={checked} onCheckedChange={onCheckedChange} className="cursor-auto" />
<Switch id={id} checked={checked} onCheckedChange={onCheckedChange} />
</div>
)
}

View File

@ -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"),
}),
]}
/>
</div>

View File

@ -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 ? (
<Logo className="aspect-square size-4" />

View File

@ -73,7 +73,7 @@ export function Component() {
{subscriptions.data?.[category].map((subscription) => (
<div key={subscription.feedId} className="group relative border-b py-5">
<a
className="flex flex-1 cursor-default"
className="flex flex-1 cursor-button"
href={`/feed/${subscription.feedId}`}
target="_blank"
>

View File

@ -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"
>
<i className="i-mgc-download-2-cute-re size-3.5 opacity-80" />
</button>

View File

@ -19,7 +19,7 @@ function Layout() {
{settings.map((t) => (
<Link
key={t.path}
className={`my-1 flex cursor-default items-center rounded-md px-2.5 py-0.5 leading-loose text-theme-foreground/70 transition-colors dark:text-theme-foreground/90 ${
className={`my-1 flex cursor-menu items-center rounded-md px-2.5 py-0.5 leading-loose text-theme-foreground/70 transition-colors dark:text-theme-foreground/90 ${
tab === t.path ? "bg-native-active text-theme-foreground/90" : ""
}`}
to={`/settings/${t.path}`}

View File

@ -27,10 +27,11 @@ const useUISettingSync = () => {
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) {

View File

@ -21,7 +21,7 @@ body,
}
button {
@apply cursor-default;
cursor: var(--pointer);
}
.prose a {

View File

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

View File

@ -4,3 +4,4 @@
@import "./colors.css";
@import "./additional.css";
@import "./scrollbar.css";
@import "./cursor.css";

View File

@ -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": "<HeartIcon />Love our product? <Link>Give us a star on GitHub!</Link>",
"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",

View File

@ -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": "<HeartIcon />喜欢我们的产品吗? <Link>在 GitHub 上给我们 star 吧!</Link>",
"feeds.claimTips": "要认证你的订阅源并接收打赏,请在订阅列表中右键点击订阅源并选择“认证”。",
"feeds.noFeeds": "没有已认证的订阅源",

View File

@ -22,6 +22,7 @@ export interface UISettings {
modalDraggable: boolean
modalOpaque: boolean
reduceMotion: boolean
usePointerCursor: boolean | null
uiFontFamily: string
readerFontFamily: string
readerRenderInlineStyle: boolean

View File

@ -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) / <alpha-value>)",
background: "hsl(var(--background) / <alpha-value>)",