mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 03:55:38 -04:00
Migrate the entire monorepo from Biome 2.4.7 to oxlint 1.56.0 (linter) and oxfmt 0.41.0 (formatter) for faster lint/format and broader rule coverage. - Add `.oxlintrc.json` with React, TypeScript, unicorn, import plugins and correctness/suspicious categories - Add `.oxfmtrc.json` with 2-space indent, import sorting, and Tailwind class sorting (all 30+ custom className attributes migrated) - Add `docs/.oxlintrc.json` and `docs/.oxfmtrc.json` with Next.js plugin - Update all 12 workspace package.json scripts: `oxlint`, `oxfmt`, `oxfmt --check` - Add `format:check` turbo task and CI step - Update VS Code settings/extensions to use `oxc.oxc-vscode` - Update CI path triggers from `biome.json` to new config files - Remove all `biome-ignore` comments and fix shadowed variables - Delete `biome.json` and `docs/biome.json` - Reformat entire codebase with oxfmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
import { useLingui } from "@lingui/react/macro";
|
|
import { IconCheck, IconPlayerPlayFilled, IconPlus, IconX } from "@tabler/icons-react";
|
|
import { AnimatePresence, motion } from "motion/react";
|
|
|
|
interface StatusButtonProps {
|
|
currentStatus: string | null;
|
|
onChange: (status: string | null) => void;
|
|
}
|
|
|
|
export function StatusButton({ currentStatus, onChange }: StatusButtonProps) {
|
|
const { t } = useLingui();
|
|
|
|
const watchingStyle = {
|
|
label: t`Watching`,
|
|
icon: IconPlayerPlayFilled,
|
|
class: "text-status-watching",
|
|
bgClass: "bg-status-watching/10 hover:bg-status-watching/15",
|
|
borderClass: "ring-status-watching/20",
|
|
};
|
|
|
|
const statusConfig = {
|
|
watchlist: watchingStyle,
|
|
in_progress: watchingStyle,
|
|
completed: {
|
|
label: t`Completed`,
|
|
icon: IconCheck,
|
|
class: "text-status-completed",
|
|
bgClass: "bg-status-completed/10 hover:bg-status-completed/15",
|
|
borderClass: "ring-status-completed/20",
|
|
},
|
|
} as const;
|
|
|
|
const config = statusConfig[currentStatus as keyof typeof statusConfig] ?? null;
|
|
|
|
return (
|
|
<AnimatePresence mode="wait" initial={false}>
|
|
{!config ? (
|
|
<motion.button
|
|
key="add"
|
|
type="button"
|
|
onClick={() => onChange("watchlist")}
|
|
initial={{ opacity: 0, y: 4 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -4 }}
|
|
transition={{ duration: 0.15 }}
|
|
className="bg-primary/10 text-primary ring-primary/20 hover:bg-primary/15 hover:ring-primary/30 inline-flex h-9 items-center gap-2 rounded-lg px-4 text-sm font-medium ring-1 transition-all active:scale-[0.97]"
|
|
>
|
|
<IconPlus aria-hidden={true} className="size-3.5" strokeWidth={2.5} />
|
|
{t`Watchlist`}
|
|
</motion.button>
|
|
) : (
|
|
<motion.button
|
|
key="status"
|
|
type="button"
|
|
onClick={() => onChange(null)}
|
|
initial={{ opacity: 0, y: 4 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -4 }}
|
|
transition={{ duration: 0.15 }}
|
|
title={t`Remove from library`}
|
|
className={`group inline-flex h-9 items-center gap-2 rounded-lg px-4 text-sm font-medium ring-1 transition-all active:scale-[0.97] ${config.class} ${config.bgClass} ${config.borderClass} hover:!bg-destructive/10 hover:!text-destructive hover:!ring-destructive/30`}
|
|
>
|
|
<span className="grid [&>svg]:col-start-1 [&>svg]:row-start-1">
|
|
<config.icon
|
|
aria-hidden={true}
|
|
className="size-3.5 transition-opacity group-hover:opacity-0"
|
|
/>
|
|
<IconX
|
|
aria-hidden={true}
|
|
className="text-destructive size-3.5 opacity-0 transition-opacity group-hover:opacity-100"
|
|
/>
|
|
</span>
|
|
<span className="grid [&>span]:col-start-1 [&>span]:row-start-1">
|
|
<span className="transition-opacity group-hover:opacity-0">{config.label}</span>
|
|
<span className="opacity-0 transition-opacity group-hover:opacity-100">
|
|
{t`Remove`}
|
|
</span>
|
|
</span>
|
|
</motion.button>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|