mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-21 18:55:16 +02:00
refactor: replace button elements with Button component for improved consistency and styling across multiple UI components
This commit is contained in:
parent
23e05acc7c
commit
3d42712b3f
27 changed files with 401 additions and 424 deletions
|
|
@ -3,6 +3,7 @@
|
|||
import { Settings, Trash2, Users } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
|
|
@ -120,11 +121,13 @@ export function SearchSpaceAvatar({
|
|||
);
|
||||
|
||||
const avatarButton = (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"relative flex items-center justify-center rounded-lg font-semibold text-white transition-all select-none",
|
||||
"relative rounded-lg font-semibold text-white transition-all select-none",
|
||||
"hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
sizeClasses,
|
||||
isActive && "ring-2 ring-primary ring-offset-1 ring-offset-background"
|
||||
|
|
@ -144,7 +147,7 @@ export function SearchSpaceAvatar({
|
|||
<Users className={cn(size === "sm" ? "h-2 w-2" : "h-2.5 w-2.5")} />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
|
||||
const menuItems = (
|
||||
|
|
|
|||
|
|
@ -347,8 +347,9 @@ export function AllSharedChatsSidebarContent({
|
|||
return (
|
||||
<div key={thread.id} className="group/item relative w-full">
|
||||
{isMobile ? (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
if (wasLongPress()) return;
|
||||
handleThreadClick(thread.id);
|
||||
|
|
@ -361,7 +362,7 @@ export function AllSharedChatsSidebarContent({
|
|||
onTouchMove={longPressHandlers.onTouchMove}
|
||||
disabled={isBusy}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 overflow-hidden rounded-md px-2 py-1.5 text-sm text-left",
|
||||
"h-auto w-full justify-start gap-2 overflow-hidden rounded-md px-2 py-1.5 text-sm text-left",
|
||||
"group-hover/item:bg-accent group-hover/item:text-accent-foreground",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
||||
isActive && "bg-accent text-accent-foreground",
|
||||
|
|
@ -369,16 +370,17 @@ export function AllSharedChatsSidebarContent({
|
|||
)}
|
||||
>
|
||||
<span className="truncate">{thread.title || "New Chat"}</span>
|
||||
</button>
|
||||
</Button>
|
||||
) : (
|
||||
<Tooltip delayDuration={600}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => handleThreadClick(thread.id)}
|
||||
disabled={isBusy}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 overflow-hidden rounded-md px-2 py-1.5 text-sm text-left",
|
||||
"h-auto w-full justify-start gap-2 overflow-hidden rounded-md px-2 py-1.5 text-sm text-left",
|
||||
"group-hover/item:bg-accent group-hover/item:text-accent-foreground",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
||||
isActive && "bg-accent text-accent-foreground",
|
||||
|
|
@ -386,7 +388,7 @@ export function AllSharedChatsSidebarContent({
|
|||
)}
|
||||
>
|
||||
<span className="truncate">{thread.title || "New Chat"}</span>
|
||||
</button>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" align="start">
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -56,19 +56,20 @@ export function ChatListItem({
|
|||
|
||||
return (
|
||||
<div className="group/item relative w-full">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={handleClick}
|
||||
{...(isMobile ? longPressHandlers : {})}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 overflow-hidden rounded-md px-2 py-1.5 text-sm text-left",
|
||||
"h-auto w-full justify-start gap-2 overflow-hidden px-2 py-1.5 text-left font-normal",
|
||||
"group-hover/item:bg-accent group-hover/item:text-accent-foreground",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
||||
isActive && "bg-accent text-accent-foreground"
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{animatedName}</span>
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
{/* Actions dropdown - trigger hidden on mobile, long-press opens it instead */}
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -575,14 +575,15 @@ export function InboxSidebarContent({
|
|||
{t("filter") || "Filter"}
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setActiveFilter("all");
|
||||
setFilterDrawerOpen(false);
|
||||
}}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
"h-auto w-full justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
activeFilter === "all"
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
|
|
@ -593,15 +594,16 @@ export function InboxSidebarContent({
|
|||
<span>{t("all") || "All"}</span>
|
||||
</span>
|
||||
{activeFilter === "all" && <Check className="h-4 w-4" />}
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setActiveFilter("unread");
|
||||
setFilterDrawerOpen(false);
|
||||
}}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
"h-auto w-full justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
activeFilter === "unread"
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
|
|
@ -612,16 +614,17 @@ export function InboxSidebarContent({
|
|||
<span>{t("unread") || "Unread"}</span>
|
||||
</span>
|
||||
{activeFilter === "unread" && <Check className="h-4 w-4" />}
|
||||
</button>
|
||||
</Button>
|
||||
{activeTab === "status" && (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setActiveFilter("errors");
|
||||
setFilterDrawerOpen(false);
|
||||
}}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
"h-auto w-full justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
activeFilter === "errors"
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
|
|
@ -632,7 +635,7 @@ export function InboxSidebarContent({
|
|||
<span>{t("errors_only") || "Errors only"}</span>
|
||||
</span>
|
||||
{activeFilter === "errors" && <Check className="h-4 w-4" />}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -642,14 +645,15 @@ export function InboxSidebarContent({
|
|||
{t("sources") || "Sources"}
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setSelectedSource(null);
|
||||
setFilterDrawerOpen(false);
|
||||
}}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
"h-auto w-full justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
selectedSource === null
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
|
|
@ -660,17 +664,18 @@ export function InboxSidebarContent({
|
|||
<span>{t("all_sources") || "All sources"}</span>
|
||||
</span>
|
||||
{selectedSource === null && <Check className="h-4 w-4" />}
|
||||
</button>
|
||||
</Button>
|
||||
{statusSourceOptions.map((source) => (
|
||||
<button
|
||||
<Button
|
||||
key={source.key}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setSelectedSource(source.key);
|
||||
setFilterDrawerOpen(false);
|
||||
}}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
"h-auto w-full justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
||||
selectedSource === source.key
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
|
|
@ -681,7 +686,7 @@ export function InboxSidebarContent({
|
|||
<span>{source.displayName}</span>
|
||||
</span>
|
||||
{selectedSource === source.key && <Check className="h-4 w-4" />}
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -922,11 +927,12 @@ export function InboxSidebarContent({
|
|||
{activeTab === "status" ? (
|
||||
<Tooltip delayDuration={600}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => handleItemClick(item)}
|
||||
disabled={isMarkingAsRead}
|
||||
className="flex items-center gap-3 flex-1 min-w-0 text-left overflow-hidden"
|
||||
className="h-auto flex-1 justify-start gap-3 overflow-hidden bg-transparent p-0 text-left hover:bg-transparent"
|
||||
>
|
||||
<div className="shrink-0">{getStatusIcon(item)}</div>
|
||||
<div className="flex-1 min-w-0 overflow-hidden">
|
||||
|
|
@ -942,7 +948,7 @@ export function InboxSidebarContent({
|
|||
{convertRenderedToDisplay(item.message)}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" align="start" className="max-w-[250px]">
|
||||
<p className="font-medium">{item.title}</p>
|
||||
|
|
@ -952,11 +958,12 @@ export function InboxSidebarContent({
|
|||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => handleItemClick(item)}
|
||||
disabled={isMarkingAsRead}
|
||||
className="flex items-center gap-3 flex-1 min-w-0 text-left overflow-hidden"
|
||||
className="h-auto flex-1 justify-start gap-3 overflow-hidden bg-transparent p-0 text-left hover:bg-transparent"
|
||||
>
|
||||
<div className="shrink-0">{getStatusIcon(item)}</div>
|
||||
<div className="flex-1 min-w-0 overflow-hidden">
|
||||
|
|
@ -972,7 +979,7 @@ export function InboxSidebarContent({
|
|||
{convertRenderedToDisplay(item.message)}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-end gap-1.5 shrink-0 w-10">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useParams } from "next/navigation";
|
|||
import { useTranslations } from "next-intl";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useIsAnonymous } from "@/contexts/anonymous-mode";
|
||||
|
|
@ -204,13 +205,14 @@ export function Sidebar({
|
|||
alwaysShowAction={!disableTooltips && isSharedChatsPanelOpen}
|
||||
action={
|
||||
onViewAllSharedChats ? (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onViewAllSharedChats}
|
||||
className="text-xs font-medium text-muted-foreground/60 hover:text-muted-foreground transition-colors whitespace-nowrap cursor-pointer bg-transparent border-none p-0 focus:outline-none"
|
||||
className="h-auto cursor-pointer whitespace-nowrap bg-transparent p-0 text-xs font-medium text-muted-foreground/60 transition-colors hover:bg-transparent hover:text-muted-foreground"
|
||||
>
|
||||
{!disableTooltips && isSharedChatsPanelOpen ? t("hide") : t("show_all")}
|
||||
</button>
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
|
|
@ -260,13 +262,14 @@ export function Sidebar({
|
|||
alwaysShowAction={!disableTooltips && isPrivateChatsPanelOpen}
|
||||
action={
|
||||
onViewAllPrivateChats ? (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onViewAllPrivateChats}
|
||||
className="text-xs font-medium text-muted-foreground/60 hover:text-muted-foreground transition-colors whitespace-nowrap cursor-pointer bg-transparent border-none p-0 focus:outline-none"
|
||||
className="h-auto cursor-pointer whitespace-nowrap bg-transparent p-0 text-xs font-medium text-muted-foreground/60 transition-colors hover:bg-transparent hover:text-muted-foreground"
|
||||
>
|
||||
{!disableTooltips && isPrivateChatsPanelOpen ? t("hide") : t("show_all")}
|
||||
</button>
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -12,18 +12,12 @@ interface SidebarButtonProps {
|
|||
isCollapsed?: boolean;
|
||||
isActive?: boolean;
|
||||
badge?: React.ReactNode;
|
||||
/** Overlay in the top-right corner of the collapsed icon (e.g. status badge) */
|
||||
collapsedOverlay?: React.ReactNode;
|
||||
/** Custom icon node for collapsed mode — overrides the default <Icon> rendering */
|
||||
collapsedIconNode?: React.ReactNode;
|
||||
/** Custom icon node for expanded mode — overrides the default <Icon> rendering */
|
||||
expandedIconNode?: React.ReactNode;
|
||||
/** Optional inline trailing content shown in expanded mode */
|
||||
trailingContent?: React.ReactNode;
|
||||
/** Optional tooltip content that replaces the default label tooltip */
|
||||
tooltipContent?: React.ReactNode;
|
||||
className?: string;
|
||||
/** Extra attributes spread onto the inner <button> (e.g. data-joyride) */
|
||||
buttonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useLocaleContext } from "@/contexts/LocaleContext";
|
||||
import { usePlatform } from "@/hooks/use-platform";
|
||||
|
|
@ -204,11 +205,12 @@ export function SidebarUserProfile({
|
|||
<div className="border-t px-1.5 py-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"mx-auto flex h-9 w-9 items-center justify-center rounded-full",
|
||||
"transition-opacity hover:opacity-90",
|
||||
"mx-auto h-9 w-9 rounded-full p-0",
|
||||
"transition-opacity hover:bg-transparent hover:opacity-90",
|
||||
"focus:outline-none focus-visible:outline-none",
|
||||
"data-[state=open]:opacity-90"
|
||||
)}
|
||||
|
|
@ -220,7 +222,7 @@ export function SidebarUserProfile({
|
|||
size="md"
|
||||
/>
|
||||
<span className="sr-only">{displayName}</span>
|
||||
</button>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="w-48" side="right" align="end" sideOffset={8}>
|
||||
|
|
@ -367,10 +369,11 @@ export function SidebarUserProfile({
|
|||
<div className="border-t">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 px-2 py-3 text-left",
|
||||
"h-auto w-full justify-start gap-2 rounded-none px-2 py-3 text-left",
|
||||
"hover:bg-accent transition-colors",
|
||||
"focus:outline-none focus-visible:outline-none",
|
||||
"data-[state=open]:bg-transparent"
|
||||
|
|
@ -386,7 +389,7 @@ export function SidebarUserProfile({
|
|||
|
||||
{/* Chevron icon */}
|
||||
<ChevronUp className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="w-48" side="top" align="center" sideOffset={4}>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
type Tab,
|
||||
tabsAtom,
|
||||
} from "@/atoms/tabs/tabs.atom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface TabBarProps {
|
||||
|
|
@ -189,22 +190,25 @@ export function TabBar({
|
|||
)}
|
||||
/>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
data-tab-id={tab.id}
|
||||
onClick={() => handleTabClick(tab)}
|
||||
onMouseEnter={() => setHoveredTabIndex(index)}
|
||||
onMouseLeave={() => setHoveredTabIndex(null)}
|
||||
className={cn(
|
||||
"group relative flex h-full items-center px-3 w-[180px] min-h-0 overflow-hidden text-[13px] font-medium rounded-md transition-colors duration-150 shrink-0",
|
||||
isActive
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
)}
|
||||
className="group relative h-full w-[180px] shrink-0"
|
||||
>
|
||||
<span className="block min-w-0 flex-1 whitespace-nowrap overflow-hidden text-left">
|
||||
{tab.title}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => handleTabClick(tab)}
|
||||
onMouseEnter={() => setHoveredTabIndex(index)}
|
||||
onMouseLeave={() => setHoveredTabIndex(null)}
|
||||
className={cn(
|
||||
"h-full w-full justify-start overflow-hidden px-3 text-left text-[13px] font-medium transition-colors duration-150",
|
||||
isActive
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground group-hover:bg-accent group-hover:text-accent-foreground group-focus-within:bg-accent group-focus-within:text-accent-foreground"
|
||||
)}
|
||||
>
|
||||
<span className="block min-w-0 flex-1 truncate text-left">{tab.title}</span>
|
||||
</Button>
|
||||
{/* Hover-only gradient + close overlay (sidebar pattern) — keeps pill width fixed and avoids ellipsis shift. */}
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -213,23 +217,19 @@ export function TabBar({
|
|||
"bg-gradient-to-l from-accent from-60% to-transparent"
|
||||
)}
|
||||
>
|
||||
{/* biome-ignore lint/a11y/useSemanticElements: cannot nest button inside button */}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => handleTabClose(e, tab.id)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
handleTabClose(e as unknown as React.MouseEvent, tab.id);
|
||||
}
|
||||
}}
|
||||
className="pointer-events-auto rounded-full p-0.5 transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||
onMouseEnter={() => setHoveredTabIndex(index)}
|
||||
onMouseLeave={() => setHoveredTabIndex(null)}
|
||||
className="pointer-events-auto size-4 rounded-full p-0.5 hover:bg-accent hover:text-accent-foreground"
|
||||
>
|
||||
<X className="size-3" />
|
||||
</span>
|
||||
<X data-icon="inline-start" />
|
||||
</Button>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
|
@ -243,14 +243,16 @@ export function TabBar({
|
|||
"before:bg-gradient-to-r before:from-transparent before:to-panel"
|
||||
)}
|
||||
>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onNewChat}
|
||||
className="flex h-8 w-8 items-center justify-center shrink-0 rounded-md text-muted-foreground transition-all duration-150 hover:bg-accent hover:text-accent-foreground"
|
||||
className="size-8 shrink-0 text-muted-foreground transition-all duration-150 hover:bg-accent hover:text-accent-foreground"
|
||||
title="New Chat"
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
<Plus data-icon="inline-start" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue