Files
sofa/apps/web/src/components/titles/status-button.tsx
T
jakeandClaude Opus 4.6 2b0c683b7b refactor: replace Biome with oxlint + oxfmt
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>
2026-03-18 13:26:50 -04:00

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