Match desktop card hover states to Nova

This commit is contained in:
Sreeram Sreedhar 2026-06-23 23:11:27 -07:00
parent cb36d89d9c
commit 47542ccf51
2 changed files with 134 additions and 91 deletions

View file

@ -10,7 +10,7 @@ import {
} from "@ui/components/card"
import { Button } from "@ui/components/button"
import { cn } from "@lib/utils"
import { Check, FolderOpen } from "lucide-react"
import { Check, FolderOpen, Info } from "lucide-react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { useCallback, useEffect, useState } from "react"
@ -570,15 +570,11 @@ const SHORTCUT_OPTIONS = [
{ label: "⌘ ⌥ M", accelerator: "CommandOrControl+Option+M" },
] as const
const modalCardStyle = {
boxShadow:
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
}
const novaCardClassName =
"group relative flex min-h-[190px] flex-col overflow-hidden rounded-[12px] bg-[#14161A] p-5 shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)] transition-colors hover:bg-[#16181D] focus-within:ring-2 focus-within:ring-[#4BA0FA]/45"
const inputBevelStyle = {
boxShadow:
"0px 1px 2px 0px rgba(0,43,87,0.1), inset 0px 0px 0px 1px rgba(43,49,67,0.08), inset 0px 1px 1px 0px rgba(0,0,0,0.08), inset 0px 2px 4px 0px rgba(0,0,0,0.02)",
}
const novaIconBoxClassName =
"flex size-12 shrink-0 items-center justify-center rounded-[10px] bg-[#080B0F] shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.6)]"
function SettingsToolCard({
tool,
@ -594,49 +590,32 @@ function SettingsToolCard({
return (
<div
className={cn(
"flex min-h-[190px] flex-col rounded-[18px] bg-[#1B1F24] p-5 transition-colors",
novaCardClassName,
tool.connected && "ring-1 ring-[#2261CA33]",
)}
style={modalCardStyle}
>
<div className="grid grid-cols-[48px_minmax(0,1fr)_auto] items-start gap-3">
<div className="pt-0.5">
<div
className="flex size-12 shrink-0 items-center justify-center rounded-[12px] border border-[rgba(82,89,102,0.2)] bg-[#14161A]"
style={inputBevelStyle}
>
<img
src={tool.iconSrc}
alt=""
className="size-7 object-contain"
draggable={false}
/>
</div>
<SettingsCardInfoButton href={tool.docsUrl} name={tool.name} />
<div className="flex items-start justify-between gap-3">
<div className={novaIconBoxClassName}>
<img
src={tool.iconSrc}
alt=""
className="size-7 object-contain"
draggable={false}
/>
</div>
<div className="min-w-0 pr-1">
<p className="font-semibold text-[#FAFAFA] text-[15px] leading-tight">
</div>
<div className="mt-8 min-w-0">
<div className="flex min-w-0 items-center gap-2">
<p className="min-w-0 truncate font-semibold text-[#FAFAFA] text-[20px] leading-tight">
{tool.name}
</p>
<p className="mt-1 line-clamp-2 font-medium text-[#737373] text-[12px] leading-[1.35]">
{tool.description}
</p>
{tool.id !== "codex" ? <SettingsInlineChip label="Pro" /> : null}
</div>
{tool.connected ? (
<span className="mt-1 inline-flex shrink-0 items-center gap-1 font-semibold text-[#4BA0FA] text-[11px] uppercase tracking-[0.08em]">
<Check className="size-3" />
Connected
</span>
) : (
<Button
type="button"
variant="insideOut"
disabled={busy}
onClick={onPreviewConnect}
className="h-9 shrink-0 gap-1.5 rounded-full px-4 font-medium text-[#FAFAFA] text-[13px]"
>
{tool.primaryAction === "connect" ? "Connect" : "Set up"}
</Button>
)}
<p className="mt-2 line-clamp-2 font-medium text-[#A1A1AA] text-[14px] leading-[1.45]">
{tool.description}
</p>
</div>
<ul className="mt-4 space-y-1.5">
@ -656,6 +635,12 @@ function SettingsToolCard({
<div className="mt-auto flex items-end justify-between gap-3 pt-4">
<div className="min-w-0">
{tool.connected ? (
<p className="mb-1 inline-flex items-center gap-1 font-semibold text-[#4BA0FA] text-[11px] uppercase tracking-[0.08em]">
<Check className="size-3" />
Connected
</p>
) : null}
<p
className="truncate font-mono font-medium text-[#737373] text-[11px]"
title={tool.configPath}
@ -683,11 +668,53 @@ function SettingsToolCard({
>
Disconnect
</Button>
) : null}
) : (
<Button
type="button"
variant="insideOut"
disabled={busy}
onClick={onPreviewConnect}
className="mt-4 h-9 rounded-full px-4 font-medium text-[#FAFAFA] text-[13px]"
>
{tool.primaryAction === "connect" ? "Connect" : "Set up"}
</Button>
)}
</div>
)
}
function SettingsCardInfoButton({
href,
name,
}: {
href: string
name: string
}) {
return (
<a
href={href}
target="_blank"
rel="noreferrer"
aria-label={`View ${name} docs`}
title={`${name} docs`}
onClick={(event) => {
event.stopPropagation()
}}
className="absolute top-4 right-4 z-10 flex size-8 shrink-0 items-center justify-center rounded-full bg-[#0D121A] text-[#A1A1AA] opacity-0 shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.7)] transition-all hover:text-[#FAFAFA] focus-visible:opacity-100 focus-visible:outline-none group-hover:opacity-100"
>
<Info className="size-3.5" />
</a>
)
}
function SettingsInlineChip({ label }: { label: string }) {
return (
<span className="shrink-0 font-semibold text-[#4BA0FA] text-[10px] uppercase tracking-wide">
{label}
</span>
)
}
function SettingsToolTargetChip({ label }: { label: string }) {
return (
<div

View file

@ -9,6 +9,7 @@ import {
CheckCircle2,
ChevronLeft,
FolderOpen,
Info,
Loader2,
Plus,
X,
@ -57,15 +58,11 @@ const STEP_LABELS: Record<DesktopOnboardingStep, string> = {
done: "Done",
}
const modalCardStyle = {
boxShadow:
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
}
const novaCardClassName =
"group relative flex min-h-[190px] flex-col overflow-hidden rounded-[12px] bg-[#14161A] p-5 shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)] transition-colors hover:bg-[#16181D] focus-within:ring-2 focus-within:ring-[#4BA0FA]/45"
const inputBevelStyle = {
boxShadow:
"0px 1px 2px 0px rgba(0,43,87,0.1), inset 0px 0px 0px 1px rgba(43,49,67,0.08), inset 0px 1px 1px 0px rgba(0,0,0,0.08), inset 0px 2px 4px 0px rgba(0,0,0,0.02)",
}
const novaIconBoxClassName =
"flex size-12 shrink-0 items-center justify-center rounded-[10px] bg-[#080B0F] shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.6)]"
export default function DesktopOnboardingPage() {
const router = useRouter()
@ -421,16 +418,16 @@ function FilesystemStep({
<div className="grid gap-3 md:grid-cols-2">
<div
className={cn(
"flex min-h-[190px] flex-col rounded-[18px] bg-[#15181D] p-5 transition-colors",
novaCardClassName,
mounted && "ring-1 ring-[#2261CA33]",
)}
style={modalCardStyle}
>
<CardInfoButton
href="https://supermemory.ai/docs/smfs/overview"
name="SMFS"
/>
<div className="flex items-start justify-between gap-3">
<div
className="flex size-12 shrink-0 items-center justify-center rounded-[12px] border border-[rgba(82,89,102,0.16)] bg-[#0C1016]"
style={inputBevelStyle}
>
<div className={novaIconBoxClassName}>
<FolderOpen className="size-6 text-[#8BC6FF]" />
</div>
</div>
@ -439,7 +436,7 @@ function FilesystemStep({
<p className="font-semibold text-[#FAFAFA] text-[20px] leading-tight">
Filesystem space
</p>
<p className="mt-2 line-clamp-2 font-medium text-[#9A9AA2] text-[14px] leading-[1.45]">
<p className="mt-2 line-clamp-2 font-medium text-[#A1A1AA] text-[14px] leading-[1.45]">
Local Supermemory folder for editors and command-line tools
</p>
</div>
@ -456,27 +453,25 @@ function FilesystemStep({
{error ? <p className="mt-4 text-red-300 text-sm">{error}</p> : null}
</div>
<div
className="flex min-h-[190px] flex-col rounded-[18px] bg-[#15181D] p-5 transition-colors"
style={modalCardStyle}
>
<div className={novaCardClassName}>
<CardInfoButton
href="https://supermemory.ai/docs/smfs/mount"
name="mount folder"
/>
<div className="flex items-start justify-between gap-3">
<div
className="flex size-12 shrink-0 items-center justify-center rounded-[12px] border border-[rgba(82,89,102,0.16)] bg-[#0C1016]"
style={inputBevelStyle}
>
<div className={novaIconBoxClassName}>
<Check className="size-6 text-[#FAFAFA]" />
</div>
<span className="mt-1 font-semibold text-[#4BA0FA] text-[11px] uppercase tracking-[0.08em]">
Recommended
</span>
</div>
<div className="mt-8 min-w-0">
<p className="font-semibold text-[#FAFAFA] text-[20px] leading-tight">
Mount folder
</p>
<p className="mt-2 line-clamp-2 font-medium text-[#9A9AA2] text-[14px] leading-[1.45]">
<div className="flex min-w-0 items-center gap-2">
<p className="min-w-0 truncate font-semibold text-[#FAFAFA] text-[20px] leading-tight">
Mount folder
</p>
<NovaInlineChip>Recommended</NovaInlineChip>
</div>
<p className="mt-2 line-clamp-2 font-medium text-[#A1A1AA] text-[14px] leading-[1.45]">
Choose or create the local folder SMFS should mount into
</p>
</div>
@ -605,16 +600,13 @@ function ToolCard({
return (
<div
className={cn(
"flex min-h-[190px] flex-col rounded-[18px] bg-[#15181D] p-5 transition-colors",
novaCardClassName,
tool.connected && "ring-1 ring-[#2261CA33]",
)}
style={modalCardStyle}
>
<CardInfoButton href={tool.docsUrl} name={tool.name} />
<div className="flex items-start justify-between gap-3">
<div
className="flex size-12 shrink-0 items-center justify-center rounded-[12px] border border-[rgba(82,89,102,0.16)] bg-[#0C1016]"
style={inputBevelStyle}
>
<div className={novaIconBoxClassName}>
<img
src={tool.iconSrc}
alt=""
@ -622,18 +614,16 @@ function ToolCard({
draggable={false}
/>
</div>
{tool.id !== "codex" ? (
<span className="mt-1 font-semibold text-[#4BA0FA] text-[11px] uppercase tracking-[0.08em]">
Pro
</span>
) : null}
</div>
<div className="mt-8 min-w-0">
<p className="font-semibold text-[#FAFAFA] text-[20px] leading-tight">
{tool.name}
</p>
<p className="mt-2 line-clamp-2 font-medium text-[#9A9AA2] text-[14px] leading-[1.45]">
<div className="flex min-w-0 items-center gap-2">
<p className="min-w-0 truncate font-semibold text-[#FAFAFA] text-[20px] leading-tight">
{tool.name}
</p>
{tool.id !== "codex" ? <NovaInlineChip>Pro</NovaInlineChip> : null}
</div>
<p className="mt-2 line-clamp-2 font-medium text-[#A1A1AA] text-[14px] leading-[1.45]">
{tool.tagline}
</p>
</div>
@ -665,6 +655,32 @@ function ToolCard({
)
}
function CardInfoButton({ href, name }: { href?: string; name: string }) {
return (
<a
href={href}
target="_blank"
rel="noreferrer"
aria-label={`View ${name} docs`}
title={`${name} docs`}
onClick={(event) => {
event.stopPropagation()
}}
className="absolute top-4 right-4 z-10 flex size-8 shrink-0 items-center justify-center rounded-full bg-[#0D121A] text-[#A1A1AA] opacity-0 shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.7)] transition-all hover:text-[#FAFAFA] focus-visible:opacity-100 focus-visible:outline-none group-hover:opacity-100"
>
<Info className="size-3.5" />
</a>
)
}
function NovaInlineChip({ children }: { children: ReactNode }) {
return (
<span className="shrink-0 font-semibold text-[#4BA0FA] text-[10px] uppercase tracking-wide">
{children}
</span>
)
}
function ToolCardStatus({ tool }: { tool: DesktopToolCard }) {
if (tool.connected) {
return (