mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
feat(web): buttons on shadcn Button — house variants (primary/danger/subtle/toolbar) keep the exact look
cva variants map to the existing .pbtn/.danger-btn/.ai-btn/.btn classes, so style.css owns appearance while shadcn adds focus-ring/disabled plumbing. Migrated: modals, ShareDialog, EmptyState, Browser topbar. 53/53. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbiaaVM2ACxeRi8ySG9ybc
This commit is contained in:
co-authored by
Claude Fable 5
parent
b7ce76b72e
commit
2993049bf4
@@ -6,6 +6,7 @@ import {
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Project, ServerConfig } from "../api/types";
|
||||
import { useHeat, useTree } from "../hooks/useBrowse";
|
||||
import { urlForPath, urlForView, type Route } from "../router";
|
||||
@@ -355,14 +356,14 @@ export default function Browser(props: {
|
||||
actions={
|
||||
<>
|
||||
{canShare && (
|
||||
<button id="share-btn" className="btn icon-only" title="Share" aria-label="Share" onClick={shareNow}>
|
||||
<Button id="share-btn" variant="toolbar" className="icon-only" title="Share" aria-label="Share" onClick={shareNow}>
|
||||
<Icon name="share" />
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
{canHistory && !path && !route.view && (
|
||||
<button id="history-btn" className="btn" onClick={historyNow}>
|
||||
<Button id="history-btn" variant="toolbar" onClick={historyNow}>
|
||||
<Icon name="hist" /> <span className="lbl">History</span>
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
{canDownload && (
|
||||
<a id="download" hidden download href={downloadURL} ref={downloadRef}>
|
||||
@@ -370,9 +371,10 @@ export default function Browser(props: {
|
||||
</a>
|
||||
)}
|
||||
{canMore && (
|
||||
<button
|
||||
<Button
|
||||
id="more-btn"
|
||||
className="btn icon-only"
|
||||
variant="toolbar"
|
||||
className="icon-only"
|
||||
title="More actions"
|
||||
aria-label="More actions"
|
||||
onClick={(e) => {
|
||||
@@ -381,7 +383,7 @@ export default function Browser(props: {
|
||||
}}
|
||||
>
|
||||
<Icon name="dots" />
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
{moreOpen && (
|
||||
<div id="more-menu" role="menu">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useRef } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "../toast";
|
||||
|
||||
// Onboarding: a signed-in account with no projects shouldn't hit a blank
|
||||
@@ -34,9 +35,9 @@ export function EmptyState({
|
||||
<p>A teammate can send you a join link. Paste it here:</p>
|
||||
<div className="ob-row">
|
||||
<input id="ob-invite" type="text" placeholder="https://…/join/…" autoComplete="off" ref={invite} />
|
||||
<button id="ob-join" className="pbtn" onClick={join}>
|
||||
<Button id="ob-join" variant="primary" onClick={join}>
|
||||
Join
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -45,9 +46,9 @@ export function EmptyState({
|
||||
<p>Create a shared space for your team's files.</p>
|
||||
<div className="ob-row">
|
||||
<input id="ob-name" type="text" placeholder="Project name, e.g. wiki" autoComplete="off" ref={name} />
|
||||
<button id="ob-create" className="pbtn" onClick={() => onCreate(name.current!.value.trim())}>
|
||||
<Button id="ob-create" variant="primary" onClick={() => onCreate(name.current!.value.trim())}>
|
||||
Create
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { api } from "../api/http";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { copyText } from "../util";
|
||||
import { toast } from "../toast";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
||||
@@ -27,19 +28,19 @@ export function ShareDialog({
|
||||
</p>
|
||||
<div className="modal-url">{url}</div>
|
||||
<div className="modal-actions">
|
||||
<button
|
||||
className="pbtn"
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() =>
|
||||
copyText(url).then((ok) => toast(ok ? "Copied." : "Select and copy the link above."))
|
||||
}
|
||||
>
|
||||
{copied ? "Copied ✓" : "Copy link"}
|
||||
</button>
|
||||
<button className="ai-btn" onClick={() => window.open(url, "_blank")}>
|
||||
</Button>
|
||||
<Button variant="subtle" onClick={() => window.open(url, "_blank")}>
|
||||
Open
|
||||
</button>
|
||||
<button
|
||||
className="ai-del"
|
||||
</Button>
|
||||
<Button
|
||||
variant="subtle" className="ai-del"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await api("DELETE", "/api/shares/" + token);
|
||||
@@ -51,10 +52,10 @@ export function ShareDialog({
|
||||
}}
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
<button className="ai-btn" onClick={onClose}>
|
||||
</Button>
|
||||
<Button variant="subtle" onClick={onClose}>
|
||||
Done
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -10,6 +10,13 @@ const buttonVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
// House variants: class names style.css already owns, so migrated
|
||||
// call sites keep the exact BearDrive look (+ shadcn focus/disabled
|
||||
// plumbing from the base classes).
|
||||
primary: "pbtn",
|
||||
danger: "danger-btn",
|
||||
subtle: "ai-btn",
|
||||
toolbar: "btn",
|
||||
destructive:
|
||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
||||
outline:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useRef, useSyncExternalStore } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -108,12 +109,12 @@ function PromptBody({ m }: { m: Prompt }) {
|
||||
onKeyDown={(e) => e.key === "Enter" && ok()}
|
||||
/>
|
||||
<div className="modal-actions">
|
||||
<button className="ai-btn" onClick={() => done(null)}>
|
||||
<Button variant="subtle" onClick={() => done(null)}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="pbtn" onClick={ok}>
|
||||
</Button>
|
||||
<Button variant="primary" onClick={ok}>
|
||||
{m.okLabel}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -131,12 +132,12 @@ function ConfirmBody({ m }: { m: Confirm }) {
|
||||
</DialogTitle>
|
||||
<p className="modal-msg">{m.message}</p>
|
||||
<div className="modal-actions">
|
||||
<button className="ai-btn" onClick={() => done(false)}>
|
||||
<Button variant="subtle" onClick={() => done(false)}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className={m.danger ? "danger-btn" : "pbtn"} onClick={() => done(true)} autoFocus>
|
||||
</Button>
|
||||
<Button variant={m.danger ? "danger" : "primary"} onClick={() => done(true)} autoFocus>
|
||||
{m.confirmLabel}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>BearDrive</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🐻</text></svg>">
|
||||
<script type="module" crossorigin src="/assets/index-CZBsmcK-.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Ky-4qqmy.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DagN_NN8.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user