"use client";
import {
CheckIcon,
ChevronRightIcon,
LogOutIcon,
type LucideIcon,
MonitorIcon,
MoonIcon,
SunIcon,
} from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { type KeyboardEvent, useEffect, useId, useRef, useState } from "react";
import { Kbd } from "@/components/ui/kbd";
import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmented-control";
import { cn } from "@/lib/utils";
export type ThemeChoice = "light" | "dark" | "system";
export interface AccountCardItem {
id: string;
label: string;
icon: LucideIcon;
shortcut?: string;
meta?: string;
}
export interface AccountCardStep {
id: string;
label: string;
done?: boolean;
}
export interface AccountCardProps {
user: { name: string; email: string; initials: string; plan?: string };
items: AccountCardItem[];
steps?: AccountCardStep[];
theme?: ThemeChoice;
onThemeChange?: (theme: ThemeChoice) => void;
onSelect?: (id: string) => void;
onSignOut?: () => void;
className?: string;
}
const spring = { type: "spring", stiffness: 420, damping: 34 } as const;
function Rolling({ value }: { value: number }) {
const reduce = useReducedMotion();
const prev = useRef(value);
const dir = value >= prev.current ? 1 : -1;
useEffect(() => {
prev.current = value;
}, [value]);
return (
{value}
);
}
const THEMES: { value: ThemeChoice; icon: LucideIcon; label: string }[] = [
{ value: "light", icon: SunIcon, label: "Light" },
{ value: "dark", icon: MoonIcon, label: "Dark" },
{ value: "system", icon: MonitorIcon, label: "System" },
];
function ThemeSwitch({ value, onChange }: { value: ThemeChoice; onChange: (t: ThemeChoice) => void }) {
const refs = useRef<(HTMLButtonElement | null)[]>([]);
const index = THEMES.findIndex((t) => t.value === value);
const onKeyDown = (e: KeyboardEvent) => {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
e.preventDefault();
const next = (index + (e.key === "ArrowRight" ? 1 : -1) + THEMES.length) % THEMES.length;
onChange(THEMES[next].value);
refs.current[next]?.focus();
};
return (
{THEMES.map((t, i) => {
const active = t.value === value;
const Icon = t.icon;
return (
onChange(t.value)}
ref={(el: HTMLButtonElement | null) => {
refs.current[i] = el;
}}
role="radio"
tabIndex={active ? 0 : -1}
title={t.label}
>
);
})}
);
}
export function AccountCard({
user,
items,
steps: initialSteps = [],
theme: themeProp,
onThemeChange,
onSelect,
onSignOut,
className,
}: AccountCardProps) {
const reduce = useReducedMotion();
const hoverId = useId();
const [steps, setSteps] = useState(initialSteps);
const [themeState, setThemeState] = useState(themeProp ?? "system");
const [hover, setHover] = useState(null);
const theme = themeProp ?? themeState;
const doneCount = steps.filter((s) => s.done).length;
const pct = steps.length ? Math.round((doneCount / steps.length) * 100) : 100;
const next = steps.find((s) => !s.done);
const r = 25;
const c = 2 * Math.PI * r;
const setTheme = (t: ThemeChoice) => {
setThemeState(t);
onThemeChange?.(t);
};
return (
{user.initials}
{pct === 100 && (
)}
{user.name}
{user.plan && (
{user.plan}
)}
{user.email}
{steps.length > 0 && (
Profile setup
%
{next ? (
setSteps((s) => s.map((x) => (x.id === next.id ? { ...x, done: true } : x)))}
transition={spring}
type="button"
>
Next: {next.label}
) : (
All set – your profile is complete
)}
)}
Theme
);
}