chore(nova): ux improvments (#820)

This commit is contained in:
MaheshtheDev 2026-04-02 18:20:34 +00:00
parent 7b64ce1e10
commit 7cfaacc806
9 changed files with 314 additions and 162 deletions

View file

@ -1,6 +1,6 @@
"use client"
import { Logo } from "@ui/assets/Logo"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { UserProfileMenu } from "@/components/user-profile-menu"
import { useAuth } from "@lib/auth-context"
import { motion } from "motion/react"
import NovaOrb from "@/components/nova/nova-orb"
@ -14,6 +14,7 @@ import Support from "@/components/settings/support"
import { ErrorBoundary } from "@/components/error-boundary"
import { useRouter } from "next/navigation"
import { useIsMobile } from "@hooks/use-mobile"
import { useLocalStorageUsername } from "@hooks/use-local-storage-username"
import { analytics } from "@/lib/analytics"
import { Sun } from "lucide-react"
@ -137,6 +138,7 @@ export default function SettingsPage() {
const hasInitialized = useRef(false)
const router = useRouter()
const isMobile = useIsMobile()
const localStorageUsername = useLocalStorageUsername()
useEffect(() => {
if (hasInitialized.current) return
@ -163,28 +165,57 @@ export default function SettingsPage() {
window.addEventListener("hashchange", handleHashChange)
return () => window.removeEventListener("hashchange", handleHashChange)
}, [])
const headerDisplayName =
user?.displayUsername || localStorageUsername || user?.name || ""
const headerPossessive = headerDisplayName
? `${headerDisplayName.split(" ")[0]}'s`
: ""
return (
<div className="h-screen flex flex-col overflow-hidden">
<header className="flex justify-between items-center px-4 md:px-6 py-3 shrink-0">
<button
type="button"
onClick={() => router.push("/")}
className="cursor-pointer"
>
<Logo className="h-7" />
</button>
<div className="flex items-center gap-2">
{user && (
<Avatar className="border border-border h-8 w-8 md:h-10 md:w-10">
<AvatarImage src={user?.image ?? ""} />
<AvatarFallback>{user?.name?.charAt(0)}</AvatarFallback>
</Avatar>
<header className="relative z-20 flex justify-between items-center gap-3 px-4 md:px-6 py-3 shrink-0">
<nav
className={cn(
"flex items-center gap-2 sm:gap-3 min-w-0 text-sm",
dmSansClassName(),
)}
</div>
aria-label="Breadcrumb"
>
<button
type="button"
onClick={() => router.push("/")}
className={cn(
"flex items-center min-w-0 rounded-lg py-1 pr-2 -ml-1 pl-1",
"hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 transition-colors cursor-pointer text-left",
)}
>
<Logo className="h-7 shrink-0" />
{headerDisplayName ? (
<div className="flex flex-col items-start justify-center ml-2 min-w-0">
<p className="text-[#8B8B8B] text-[11px] leading-tight">
{headerPossessive}
</p>
<p className="text-white font-bold text-xl leading-none -mt-1">
supermemory
</p>
</div>
) : (
<span className="ml-2 font-medium text-white/90">
supermemory
</span>
)}
</button>
<span className="text-white/35 shrink-0" aria-hidden>
/
</span>
<span className="text-white/50 font-medium shrink-0">Settings</span>
</nav>
<UserProfileMenu />
</header>
<main className="flex-1 min-h-0 overflow-y-auto md:overflow-hidden">
<div className="flex flex-col md:flex-row md:justify-center gap-4 md:gap-8 lg:gap-12 px-4 md:px-6 pt-4 pb-6 md:h-full">
<div className="w-full md:w-auto md:min-w-[280px] shrink-0">
<div className="md:w-auto md:max-w-[380px] shrink-0">
{!isMobile && (
<motion.div
animate={{

View file

@ -10,6 +10,7 @@ import { useAuth } from "@lib/auth-context"
import { TextEditor } from "./text-editor"
import { useProject } from "@/stores"
import { useQuickNoteDraft } from "@/stores/quick-note-draft"
import { useLocalStorageUsername } from "@hooks/use-local-storage-username"
interface FullscreenNoteModalProps {
isOpen: boolean
@ -27,6 +28,7 @@ export function FullscreenNoteModal({
isSaving = false,
}: FullscreenNoteModalProps) {
const { user } = useAuth()
const localStorageUsername = useLocalStorageUsername()
const { selectedProject } = useProject()
const { setDraft } = useQuickNoteDraft(selectedProject)
const [content, setContent] = useState(initialContent)
@ -43,10 +45,7 @@ export function FullscreenNoteModal({
}, [isOpen, initialContent])
const displayName =
user?.displayUsername ||
(typeof window !== "undefined" && localStorage.getItem("username")) ||
(typeof window !== "undefined" && localStorage.getItem("userName")) ||
""
user?.displayUsername || localStorageUsername || user?.name || ""
const userName = displayName ? `${displayName.split(" ")[0]}'s` : "My"
const handleSave = useCallback(() => {

View file

@ -1,22 +1,18 @@
"use client"
import { Logo } from "@ui/assets/Logo"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { useAuth } from "@lib/auth-context"
import {
LayoutGridIcon,
Plus,
SearchIcon,
LogOut,
Settings,
Home,
Code2,
Sun,
ExternalLink,
HelpCircle,
MenuIcon,
MessageCircleIcon,
RotateCcw,
} from "lucide-react"
import { Button } from "@ui/components/button"
import { cn } from "@lib/utils"
@ -30,13 +26,13 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@ui/components/dropdown-menu"
import { authClient } from "@lib/auth"
import { useProject } from "@/stores"
import { useRouter } from "next/navigation"
import Link from "next/link"
import { SpaceSelector } from "./space-selector"
import { useIsMobile } from "@hooks/use-mobile"
import { useOrgOnboarding } from "@hooks/use-org-onboarding"
import { useLocalStorageUsername } from "@hooks/use-local-storage-username"
import { UserProfileMenu } from "@/components/user-profile-menu"
import { FeedbackModal } from "./feedback-modal"
import { useViewMode } from "@/lib/view-mode-context"
import { useQueryState } from "nuqs"
@ -53,7 +49,6 @@ export function Header({ onAddMemory, onOpenChat, onOpenSearch }: HeaderProps) {
const { selectedProjects, setSelectedProjects } = useProject()
const router = useRouter()
const isMobile = useIsMobile()
const { resetOrgOnboarded } = useOrgOnboarding()
const [feedbackOpen, setFeedbackOpen] = useQueryState(
"feedback",
feedbackParam,
@ -61,18 +56,11 @@ export function Header({ onAddMemory, onOpenChat, onOpenSearch }: HeaderProps) {
const isFeedbackOpen = feedbackOpen ?? false
const { viewMode, setViewMode } = useViewMode()
const handleTryOnboarding = () => {
resetOrgOnboarded()
router.push("/onboarding?step=input&flow=welcome")
}
const handleFeedback = () => setFeedbackOpen(true)
const localStorageUsername = useLocalStorageUsername()
const displayName =
user?.displayUsername ||
(typeof window !== "undefined" && localStorage.getItem("username")) ||
(typeof window !== "undefined" && localStorage.getItem("userName")) ||
""
user?.displayUsername || localStorageUsername || user?.name || ""
const userName = displayName ? `${displayName.split(" ")[0]}'s` : "My"
return (
<div className="flex p-3 md:p-4 justify-between items-center gap-2">
@ -316,96 +304,7 @@ export function Header({ onAddMemory, onOpenChat, onOpenSearch }: HeaderProps) {
</Button>
</>
)}
{user && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className="rounded-full cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 transition-transform hover:scale-105"
>
<Avatar className="border border-[#2E3033] h-8 w-8 md:h-10 md:w-10">
<AvatarImage src={user?.image ?? ""} />
<AvatarFallback className="bg-[#0D121A] text-white">
{user?.name?.charAt(0)}
</AvatarFallback>
</Avatar>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className={cn(
"min-w-[220px] p-1.5 rounded-xl border border-[#2E3033] shadow-[0px_1.5px_20px_0px_rgba(0,0,0,0.65)]",
dmSansClassName(),
)}
style={{
background: "linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
}}
>
<div id="user-info" className="px-3 py-2.5">
<p className="text-white text-sm font-medium truncate">
{user?.name}
</p>
<p className="text-[#737373] text-xs truncate">{user?.email}</p>
</div>
<DropdownMenuSeparator className="bg-[#2E3033]" />
<DropdownMenuItem
onClick={() => router.push("/settings")}
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<Settings className="h-4 w-4 text-[#737373]" />
Settings
</DropdownMenuItem>
<DropdownMenuItem
onClick={handleTryOnboarding}
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<RotateCcw className="h-4 w-4 text-[#737373]" />
Restart Onboarding
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-[#2E3033]" />
<DropdownMenuItem
asChild
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<a href="mailto:support@supermemory.com">
<HelpCircle className="h-4 w-4 text-[#737373]" />
Help & Support
</a>
</DropdownMenuItem>
<DropdownMenuItem
asChild
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<a
href="https://supermemory.link/discord"
target="_blank"
rel="noreferrer"
>
<svg
className="h-4 w-4 text-[#737373]"
viewBox="0 0 24 24"
fill="currentColor"
>
<title>Discord</title>
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
</svg>
Discord
</a>
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-[#2E3033]" />
<DropdownMenuItem
onClick={() => {
authClient.signOut()
router.push("/login/new")
}}
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<LogOut className="h-4 w-4 text-[#737373]" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
<UserProfileMenu />
</div>
<FeedbackModal
isOpen={isFeedbackOpen}

View file

@ -1,43 +1,66 @@
"use client"
import { motion } from "motion/react"
import { Logo } from "@ui/assets/Logo"
import { useAuth } from "@lib/auth-context"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { UserProfileMenu } from "@/components/user-profile-menu"
import { useRouter } from "next/navigation"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { useLocalStorageUsername } from "@hooks/use-local-storage-username"
export function SetupHeader() {
const { user } = useAuth()
const router = useRouter()
const localStorageUsername = useLocalStorageUsername()
const displayName =
user?.displayUsername ||
(typeof window !== "undefined" && localStorage.getItem("username")) ||
(typeof window !== "undefined" && localStorage.getItem("userName")) ||
""
user?.displayUsername || localStorageUsername || user?.name || ""
const userName = displayName ? `${displayName.split(" ")[0]}'s` : "My"
return (
<motion.div
className="flex p-6 justify-between items-center"
className="relative z-20 flex p-6 justify-between items-center"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<div className="flex items-center z-10!">
<Logo className="h-7" />
{displayName && (
<div className="flex flex-col items-start justify-center ml-2">
<p className="text-[#8B8B8B] text-[11px] leading-tight">
{userName}
</p>
<p className="text-white font-bold text-xl leading-none -mt-1">
supermemory
</p>
</div>
<nav
className={cn(
"flex items-center gap-2 sm:gap-3 min-w-0 z-10! text-sm",
dmSansClassName(),
)}
</div>
aria-label="Breadcrumb"
>
<button
type="button"
onClick={() => router.push("/")}
className={cn(
"flex items-center min-w-0 rounded-lg py-1 pr-2 -ml-1 pl-1",
"hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 transition-colors cursor-pointer text-left",
)}
>
<Logo className="h-7 shrink-0" />
{displayName ? (
<div className="flex flex-col items-start justify-center ml-2 min-w-0">
<p className="text-[#8B8B8B] text-[11px] leading-tight">
{userName}
</p>
<p className="text-white font-bold text-xl leading-none -mt-1">
supermemory
</p>
</div>
) : (
<span className="ml-2 font-medium text-white/90">supermemory</span>
)}
</button>
<span className="text-white/35 shrink-0" aria-hidden>
/
</span>
<span className="text-white/50 font-medium shrink-0">Setup</span>
</nav>
{user && (
<Avatar className="border border-border h-8 w-8 md:h-10 md:w-10 z-10!">
<AvatarImage src={user?.image ?? ""} />
<AvatarFallback>{user?.name?.charAt(0)}</AvatarFallback>
</Avatar>
<UserProfileMenu className="z-10" avatarClassName="border-border" />
)}
</motion.div>
)

View file

@ -14,6 +14,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon, Download, Copy, Check } from "lucide-react"
import { GradientLogo } from "@ui/assets/Logo"
import { useAuth } from "@lib/auth-context"
import { useLocalStorageUsername } from "@hooks/use-local-storage-username"
import { toast } from "sonner"
import * as htmlToImage from "html-to-image"
@ -32,6 +33,7 @@ const XIcon2 = ({ className }: { className?: string }) => (
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M12.6 0.75H15.054L9.694 6.89L16 15.25H11.063L7.196 10.176L2.771 15.25H0.316L6.05 8.682L0 0.75H5.063L8.558 5.391L12.6 0.75ZM11.74 13.77H13.1L4.324 2.145H2.865L11.74 13.77Z"
@ -47,6 +49,7 @@ const LinkedInIcon = ({ className }: { className?: string }) => (
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M3.58065 4.89474H0V16H3.58065V4.89474ZM1.79032 0C0.801613 0 0 0.801613 0 1.79032C0 2.77903 0.801613 3.58065 1.79032 3.58065C2.77903 3.58065 3.58065 2.77903 3.58065 1.79032C3.58065 0.801613 2.77903 0 1.79032 0ZM8.71613 4.89474H5.33871V16H8.71613V10.0645C8.71613 8.46774 9.11774 6.93548 11.1613 6.93548C13.1774 6.93548 13.2097 8.75806 13.2097 10.1613V16H16V9.48387C16 6.74194 15.3871 4.64516 12.1935 4.64516C10.6452 4.64516 9.59677 5.48387 9.16129 6.27419H9.12903V4.89474H8.71613Z"
@ -62,6 +65,7 @@ const InstagramIcon = ({ className }: { className?: string }) => (
viewBox="0 0 13 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<rect
x="1.08333"
@ -284,11 +288,9 @@ export function ShareModal({
const [copied, setCopied] = useState(false)
const previewRef = useRef<HTMLDivElement>(null)
const localStorageUsername = useLocalStorageUsername()
const displayName =
user?.displayUsername ||
(typeof window !== "undefined" ? localStorage.getItem("username") : null) ||
(typeof window !== "undefined" ? localStorage.getItem("userName") : null) ||
""
user?.displayUsername || localStorageUsername || user?.name || ""
const userName = displayName ? `${displayName.split(" ")[0]}'s` : "Your"
const capturePreview = useCallback(async (): Promise<Blob | null> => {

View file

@ -0,0 +1,141 @@
"use client"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { useAuth } from "@lib/auth-context"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@ui/components/dropdown-menu"
import { authClient } from "@lib/auth"
import { useRouter } from "next/navigation"
import { LogOut, Settings, RotateCcw, HelpCircle } from "lucide-react"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { useOrgOnboarding } from "@hooks/use-org-onboarding"
export function UserProfileMenu({
className,
avatarClassName,
}: {
className?: string
avatarClassName?: string
}) {
const { user } = useAuth()
const router = useRouter()
const { resetOrgOnboarded } = useOrgOnboarding()
const handleTryOnboarding = () => {
resetOrgOnboarded()
router.push("/onboarding?step=input&flow=welcome")
}
const handleSignOut = () => {
void (async () => {
try {
await authClient.signOut()
} catch {
// still navigate if the request fails (offline, etc.)
}
router.push("/login/new")
})()
}
if (!user) return null
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className={cn(
"rounded-full cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 transition-transform hover:scale-105",
className,
)}
>
<Avatar
className={cn(
"border border-[#2E3033] h-8 w-8 md:h-10 md:w-10",
avatarClassName,
)}
>
<AvatarImage src={user.image ?? ""} />
<AvatarFallback className="bg-[#0D121A] text-white">
{user.name?.charAt(0)}
</AvatarFallback>
</Avatar>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className={cn(
"min-w-[220px] p-1.5 rounded-xl border border-[#2E3033] shadow-[0px_1.5px_20px_0px_rgba(0,0,0,0.65)]",
dmSansClassName(),
)}
style={{
background: "linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
}}
>
<div id="user-info" className="px-3 py-2.5">
<p className="text-white text-sm font-medium truncate">{user.name}</p>
<p className="text-[#737373] text-xs truncate">{user.email}</p>
</div>
<DropdownMenuSeparator className="bg-[#2E3033]" />
<DropdownMenuItem
onClick={() => router.push("/settings")}
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<Settings className="h-4 w-4 text-[#737373]" />
Settings
</DropdownMenuItem>
<DropdownMenuItem
onClick={handleTryOnboarding}
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<RotateCcw className="h-4 w-4 text-[#737373]" />
Restart Onboarding
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-[#2E3033]" />
<DropdownMenuItem
asChild
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<a href="mailto:support@supermemory.com">
<HelpCircle className="h-4 w-4 text-[#737373]" />
Help & Support
</a>
</DropdownMenuItem>
<DropdownMenuItem
asChild
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<a
href="https://supermemory.link/discord"
target="_blank"
rel="noreferrer"
>
<svg
className="h-4 w-4 text-[#737373]"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
</svg>
Discord
</a>
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-[#2E3033]" />
<DropdownMenuItem
onClick={handleSignOut}
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<LogOut className="h-4 w-4 text-[#737373]" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View file

@ -10,19 +10,47 @@
:root {
--color-placeholder-onboarding: #525966;
--sm-scrollbar-thumb: rgb(41 57 82 / 0.55);
--sm-scrollbar-thumb-hover: rgb(55 90 130 / 0.75);
--sm-scrollbar-thumb-active: rgb(70 110 160 / 0.85);
}
* {
scrollbar-width: thin;
scrollbar-color: var(--sm-scrollbar-thumb) transparent;
}
*::-webkit-scrollbar {
width: 6px;
height: 6px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: var(--sm-scrollbar-thumb);
border-radius: 100px;
border: 2px solid transparent;
background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover {
background-color: var(--sm-scrollbar-thumb-hover);
}
*::-webkit-scrollbar-thumb:active {
background-color: var(--sm-scrollbar-thumb-active);
}
*::-webkit-scrollbar-corner {
background: transparent;
}
.scrollbar-thin {
scrollbar-width: thin;
scrollbar-color: #293952 transparent;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: transparent;
scrollbar-color: var(--sm-scrollbar-thumb) transparent;
}
.sm-tweet-theme .react-tweet-theme {

View file

@ -100,6 +100,16 @@
}
}
}
},
{
"includes": ["**/globals.css"],
"linter": {
"rules": {
"complexity": {
"noImportantStyles": "off"
}
}
}
}
],
"vcs": {

View file

@ -0,0 +1,19 @@
import * as React from "react"
/**
* `username` / `userName` keys written by the app. Populated only after mount so
* server HTML and the first client render stay aligned (no hydration mismatch).
*/
export function useLocalStorageUsername(): string {
const [value, setValue] = React.useState("")
React.useEffect(() => {
setValue(
localStorage.getItem("username") ??
localStorage.getItem("userName") ??
"",
)
}, [])
return value
}