mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-23 19:05:16 +02:00
refactor: replace button elements with Button component for improved consistency and styling across additional UI components
This commit is contained in:
parent
3d42712b3f
commit
ee72a49ab1
17 changed files with 274 additions and 263 deletions
|
|
@ -72,44 +72,49 @@ export function BuyPagesContent() {
|
|||
<div className="space-y-3">
|
||||
{/* Stepper */}
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
|
||||
disabled={quantity <= 1 || purchaseMutation.isPending}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted disabled:opacity-40"
|
||||
className="size-8 shadow-none transition-colors hover:bg-muted disabled:opacity-40"
|
||||
>
|
||||
<Minus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</Button>
|
||||
<span className="min-w-28 text-center text-lg font-semibold tabular-nums">
|
||||
{totalPages.toLocaleString()}
|
||||
</span>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setQuantity((q) => Math.min(100, q + 1))}
|
||||
disabled={quantity >= 100 || purchaseMutation.isPending}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted disabled:opacity-40"
|
||||
className="size-8 shadow-none transition-colors hover:bg-muted disabled:opacity-40"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Quick-pick presets */}
|
||||
<div className="flex flex-wrap justify-center gap-1.5">
|
||||
{PRESET_MULTIPLIERS.map((m) => (
|
||||
<button
|
||||
<Button
|
||||
key={m}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => setQuantity(m)}
|
||||
disabled={purchaseMutation.isPending}
|
||||
className={cn(
|
||||
"rounded-md border px-2.5 py-1 text-xs font-medium tabular-nums transition-colors disabled:opacity-60",
|
||||
"h-auto rounded-md border px-2.5 py-1 text-xs font-medium tabular-nums transition-colors hover:text-foreground disabled:opacity-60",
|
||||
quantity === m
|
||||
? "border-emerald-500 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
||||
: "border-border hover:border-emerald-500/40 hover:bg-muted/40"
|
||||
)}
|
||||
>
|
||||
{(m * PAGE_PACK_SIZE).toLocaleString()}
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -105,43 +105,48 @@ export function BuyTokensContent() {
|
|||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
|
||||
disabled={quantity <= 1 || purchaseMutation.isPending}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted disabled:opacity-40"
|
||||
className="size-8 shadow-none transition-colors hover:bg-muted disabled:opacity-40"
|
||||
>
|
||||
<Minus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</Button>
|
||||
<span className="min-w-32 text-center text-lg font-semibold tabular-nums">
|
||||
${(totalCreditMicros / 1_000_000).toFixed(0)} of credit
|
||||
</span>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setQuantity((q) => Math.min(100, q + 1))}
|
||||
disabled={quantity >= 100 || purchaseMutation.isPending}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted disabled:opacity-40"
|
||||
className="size-8 shadow-none transition-colors hover:bg-muted disabled:opacity-40"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-1.5">
|
||||
{PRESET_MULTIPLIERS.map((m) => (
|
||||
<button
|
||||
<Button
|
||||
key={m}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => setQuantity(m)}
|
||||
disabled={purchaseMutation.isPending}
|
||||
className={cn(
|
||||
"rounded-md border px-2.5 py-1 text-xs font-medium tabular-nums transition-colors disabled:opacity-60",
|
||||
"h-auto rounded-md border px-2.5 py-1 text-xs font-medium tabular-nums transition-colors hover:text-foreground disabled:opacity-60",
|
||||
quantity === m
|
||||
? "border-purple-500 bg-purple-500/10 text-purple-600 dark:text-purple-400"
|
||||
: "border-border hover:border-purple-500/40 hover:bg-muted/40"
|
||||
)}
|
||||
>
|
||||
${m}
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -462,47 +462,44 @@ function RolesContent({
|
|||
|
||||
return (
|
||||
<div key={role.id} className="rounded-lg border border-border/60 overflow-hidden">
|
||||
{/* biome-ignore lint/a11y/useSemanticElements: row contains nested interactive elements (DropdownMenu); using a <button> would produce invalid nested-button markup */}
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={isExpanded}
|
||||
className="flex items-center gap-4 p-4 transition-colors hover:bg-accent hover:text-accent-foreground cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={() => setExpandedRoleId(isExpanded ? null : role.id)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
setExpandedRoleId(isExpanded ? null : role.id);
|
||||
}
|
||||
}}
|
||||
className="group/role-header flex items-center gap-4 p-4 transition-colors hover:bg-accent hover:text-accent-foreground focus-within:bg-accent focus-within:text-accent-foreground"
|
||||
>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm">{role.name}</span>
|
||||
{role.is_system_role && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground font-medium">
|
||||
System
|
||||
</span>
|
||||
)}
|
||||
{role.is_default && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground font-medium">
|
||||
Default
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
aria-expanded={isExpanded}
|
||||
className="h-auto min-w-0 flex-1 justify-start gap-4 p-0 text-left font-normal hover:bg-transparent hover:text-inherit focus-visible:ring-0"
|
||||
onClick={() => setExpandedRoleId(isExpanded ? null : role.id)}
|
||||
>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm">{role.name}</span>
|
||||
{role.is_system_role && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground font-medium">
|
||||
System
|
||||
</span>
|
||||
)}
|
||||
{role.is_default && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground font-medium">
|
||||
Default
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{role.description && (
|
||||
<p className="text-xs text-muted-foreground mt-0.5 truncate">
|
||||
{role.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{role.description && (
|
||||
<p className="text-xs text-muted-foreground mt-0.5 truncate">
|
||||
{role.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="shrink-0">
|
||||
<PermissionsBadge permissions={role.permissions} />
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<PermissionsBadge permissions={role.permissions} />
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
{!role.is_system_role && (
|
||||
<div className="shrink-0" role="none" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="shrink-0">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||
|
|
@ -552,14 +549,22 @@ function RolesContent({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="shrink-0 p-1">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label={isExpanded ? `Collapse ${role.name}` : `Expand ${role.name}`}
|
||||
aria-expanded={isExpanded}
|
||||
className="size-6 shrink-0 p-1 hover:bg-transparent hover:text-inherit focus-visible:ring-0"
|
||||
onClick={() => setExpandedRoleId(isExpanded ? null : role.id)}
|
||||
>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
"h-4 w-4 text-muted-foreground transition-transform duration-200",
|
||||
isExpanded && "rotate-90"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
|
|
@ -692,40 +697,44 @@ function PermissionsEditor({
|
|||
|
||||
return (
|
||||
<div key={category} className="rounded-lg border border-border/60 overflow-hidden">
|
||||
{/* biome-ignore lint/a11y/useSemanticElements: row contains a nested interactive Checkbox; using a <button> would produce invalid nested-button markup */}
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={isExpanded}
|
||||
className="flex items-center justify-between px-3 py-2.5 hover:bg-accent hover:text-accent-foreground transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={() => toggleCategoryExpanded(category)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
toggleCategoryExpanded(category);
|
||||
}
|
||||
}}
|
||||
className="group/category-header flex items-center justify-between px-3 py-2.5 transition-colors hover:bg-accent hover:text-accent-foreground focus-within:bg-accent focus-within:text-accent-foreground"
|
||||
>
|
||||
<div className="flex-1 flex items-center gap-2.5">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
aria-expanded={isExpanded}
|
||||
className="h-auto min-w-0 flex-1 justify-start gap-2.5 p-0 text-left font-normal hover:bg-transparent hover:text-inherit focus-visible:ring-0"
|
||||
onClick={() => toggleCategoryExpanded(category)}
|
||||
>
|
||||
<IconComponent className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="font-medium text-sm">{config.label}</span>
|
||||
<span className="text-[11px] text-muted-foreground tabular-nums">
|
||||
{stats.selected}/{stats.total}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={stats.allSelected}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={() => onToggleCategory(category)}
|
||||
aria-label={`Select all ${config.label} permissions`}
|
||||
/>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
"h-4 w-4 text-muted-foreground transition-transform duration-200",
|
||||
isExpanded && "rotate-90"
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label={isExpanded ? `Collapse ${config.label}` : `Expand ${config.label}`}
|
||||
aria-expanded={isExpanded}
|
||||
className="size-6 p-1 hover:bg-transparent hover:text-inherit focus-visible:ring-0"
|
||||
onClick={() => toggleCategoryExpanded(category)}
|
||||
>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
"h-4 w-4 text-muted-foreground transition-transform duration-200",
|
||||
isExpanded && "rotate-90"
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -745,16 +754,19 @@ function PermissionsEditor({
|
|||
isSelected ? "bg-muted/60 hover:bg-accent hover:text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
|
||||
)}
|
||||
>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
className="flex-1 min-w-0 text-left cursor-pointer focus:outline-none focus-visible:outline-none"
|
||||
variant="ghost"
|
||||
className="h-auto flex-1 min-w-0 justify-start p-0 text-left font-normal hover:bg-transparent hover:text-inherit focus-visible:ring-0"
|
||||
onClick={() => onTogglePermission(perm.value)}
|
||||
>
|
||||
<span className="text-sm font-medium">{actionLabel}</span>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{perm.description}
|
||||
</p>
|
||||
</button>
|
||||
<span className="min-w-0">
|
||||
<span className="block text-sm font-medium">{actionLabel}</span>
|
||||
<span className="block text-xs text-muted-foreground truncate">
|
||||
{perm.description}
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={() => onTogglePermission(perm.value)}
|
||||
|
|
@ -871,12 +883,13 @@ function CreateRoleDialog({
|
|||
<Label className="text-sm font-medium">Start from a template</Label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{Object.entries(ROLE_PRESETS).map(([key, preset]) => (
|
||||
<button
|
||||
<Button
|
||||
key={key}
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => applyPreset(key as keyof typeof ROLE_PRESETS)}
|
||||
className={cn(
|
||||
"p-3 rounded-lg border transition-colors hover:bg-accent hover:text-accent-foreground",
|
||||
"h-auto p-3 whitespace-normal transition-colors hover:bg-accent hover:text-accent-foreground",
|
||||
"flex items-center justify-center text-center sm:block sm:text-left",
|
||||
selectedPermissions.length > 0 &&
|
||||
preset.permissions.every((p) => selectedPermissions.includes(p))
|
||||
|
|
@ -888,7 +901,7 @@ function CreateRoleDialog({
|
|||
<p className="hidden sm:block text-xs text-muted-foreground mt-0.5 line-clamp-2">
|
||||
{preset.description}
|
||||
</p>
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import type * as React from "react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
|
@ -55,12 +56,13 @@ export function SettingsDialog({
|
|||
<nav className="hidden md:flex w-[220px] shrink-0 flex-col border-r border-border p-3 pt-6">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{navItems.map((item) => (
|
||||
<button
|
||||
<Button
|
||||
key={item.value}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => onItemChange(item.value)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors text-left focus:outline-none focus-visible:outline-none",
|
||||
"h-auto justify-start gap-3 rounded-lg px-3 py-2.5 text-left text-sm font-medium transition-colors focus:outline-none focus-visible:outline-none",
|
||||
activeItem === item.value
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
|
|
@ -68,7 +70,7 @@ export function SettingsDialog({
|
|||
>
|
||||
{item.icon}
|
||||
{item.label}
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -88,13 +90,14 @@ export function SettingsDialog({
|
|||
>
|
||||
<div className="flex gap-1 px-4 pb-2">
|
||||
{navItems.map((item) => (
|
||||
<button
|
||||
<Button
|
||||
key={item.value}
|
||||
ref={activeItem === item.value ? activeRef : undefined}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => handleItemChange(item.value)}
|
||||
className={cn(
|
||||
"flex items-center gap-2 whitespace-nowrap rounded-full px-3 py-1.5 text-xs font-medium transition-colors shrink-0 focus:outline-none focus-visible:outline-none",
|
||||
"h-auto shrink-0 gap-2 rounded-full px-3 py-1.5 text-xs font-medium transition-colors focus:outline-none focus-visible:outline-none",
|
||||
activeItem === item.value
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
|
|
@ -102,7 +105,7 @@ export function SettingsDialog({
|
|||
>
|
||||
{item.icon}
|
||||
{item.label}
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue