feat(ui): shared <Button> primitive + migrate ShareDialog/VideoEditor/ChannelPage proofs

Introduce frontend/src/components/ui/Button.tsx — the one action-button primitive
(variants primary|danger|outline|ghost|glass|danger-outline; sizes xs|sm|md|lg|icon;
icon/iconOnly/label/loading/fullWidth/forwardRef). No focus-visible (already centralized
in index.css @layer base). Migrate the cleanest proof sites off btnCls, and flip the
channel Subscribe button to the outline variant so it matches Full history (the user's ask).
This commit is contained in:
2026-08-28 02:59:21 +02:00
parent 9ae6c47d6c
commit 4892c38466
5 changed files with 202 additions and 57 deletions
+11 -25
View File
@@ -2,22 +2,13 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { qk } from "../lib/queryKeys";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
Crop,
Eye,
EyeOff,
Pause,
Play,
Scissors,
SplitSquareHorizontal,
Loader2,
X,
} from "lucide-react";
import { Crop, Eye, EyeOff, Pause, Play, Scissors, SplitSquareHorizontal, X } from "lucide-react";
import clsx from "clsx";
import Modal from "./Modal";
import { notify } from "../lib/notifications";
import { api, type DownloadJob, type EditSpec } from "../lib/api";
import { inputCls, btnCls } from "./ui/form";
import { Button } from "./ui/Button";
// mm:ss.d timecode <-> seconds.
function tc(s: number): string {
@@ -621,28 +612,23 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
{reencode ? t("editor.willReencode") : t("editor.willCopy")}
</span>
<div className="flex gap-2">
<button
onClick={onClose}
className={clsx(btnCls, "text-muted hover:text-fg hover:bg-surface")}
>
<Button variant="ghost" size="sm" onClick={onClose}>
{t("common.cancel")}
</button>
<button
</Button>
<Button
variant="primary"
size="sm"
onClick={() => create.mutate()}
disabled={!valid || create.isPending}
className={clsx(btnCls, "btn-primary inline-flex items-center gap-1.5")}
disabled={!valid}
loading={create.isPending}
icon={<Scissors className="w-4 h-4" />}
>
{create.isPending ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Scissors className="w-4 h-4" />
)}
{willJoin
? t("editor.createJoin")
: kept.length > 1
? t("editor.createN", { count: kept.length })
: t("editor.create")}
</button>
</Button>
</div>
</div>
</div>