mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-24 21:38:09 +02:00
fix: update sidebar components for consistent styling and improved unread item display
- Changed text color for active and archived item counts in AllPrivateChatsSidebar and AllSharedChatsSidebar to use 'text-muted-foreground' for better visibility. - Replaced Loader2 with a new Spinner component in InboxSidebar for a consistent loading indicator. - Enhanced unread item count display in InboxSidebar to show zero when no unread items are present, improving user feedback. - Adjusted styles for MoreHorizontal and ListFilter icons in InboxSidebar to align with the updated design system.
This commit is contained in:
parent
22b2d6e400
commit
8dcdd27d10
4 changed files with 81 additions and 40 deletions
|
|
@ -280,7 +280,7 @@ export function AllPrivateChatsSidebar({
|
||||||
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
||||||
<MessageCircleMore className="h-4 w-4" />
|
<MessageCircleMore className="h-4 w-4" />
|
||||||
<span>Active</span>
|
<span>Active</span>
|
||||||
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-primary text-xs font-medium">{activeCount}</span>
|
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-muted-foreground text-xs font-medium">{activeCount}</span>
|
||||||
</span>
|
</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
|
|
@ -290,7 +290,7 @@ export function AllPrivateChatsSidebar({
|
||||||
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
||||||
<ArchiveIcon className="h-4 w-4" />
|
<ArchiveIcon className="h-4 w-4" />
|
||||||
<span>Archived</span>
|
<span>Archived</span>
|
||||||
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-primary text-xs font-medium">{archivedCount}</span>
|
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-muted-foreground text-xs font-medium">{archivedCount}</span>
|
||||||
</span>
|
</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
@ -363,7 +363,7 @@ export function AllPrivateChatsSidebar({
|
||||||
{isDeleting ? (
|
{isDeleting ? (
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
<span className="sr-only">{t("more_options") || "More options"}</span>
|
<span className="sr-only">{t("more_options") || "More options"}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,7 @@ export function AllSharedChatsSidebar({
|
||||||
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
||||||
<MessageCircleMore className="h-4 w-4" />
|
<MessageCircleMore className="h-4 w-4" />
|
||||||
<span>Active</span>
|
<span>Active</span>
|
||||||
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-primary text-xs font-medium">{activeCount}</span>
|
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-muted-foreground text-xs font-medium">{activeCount}</span>
|
||||||
</span>
|
</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
|
|
@ -290,7 +290,7 @@ export function AllSharedChatsSidebar({
|
||||||
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
||||||
<ArchiveIcon className="h-4 w-4" />
|
<ArchiveIcon className="h-4 w-4" />
|
||||||
<span>Archived</span>
|
<span>Archived</span>
|
||||||
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-primary text-xs font-medium">{archivedCount}</span>
|
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-muted-foreground text-xs font-medium">{archivedCount}</span>
|
||||||
</span>
|
</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
@ -363,7 +363,7 @@ export function AllSharedChatsSidebar({
|
||||||
{isDeleting ? (
|
{isDeleting ? (
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
<span className="sr-only">{t("more_options") || "More options"}</span>
|
<span className="sr-only">{t("more_options") || "More options"}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
History,
|
History,
|
||||||
Inbox,
|
Inbox,
|
||||||
ListFilter,
|
ListFilter,
|
||||||
Loader2,
|
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
Search,
|
Search,
|
||||||
|
|
@ -34,6 +33,7 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import type { InboxItem } from "@/hooks/use-inbox";
|
import type { InboxItem } from "@/hooks/use-inbox";
|
||||||
|
|
@ -168,16 +168,24 @@ export function InboxSidebar({
|
||||||
return items;
|
return items;
|
||||||
}, [currentTabItems, activeFilter, searchQuery]);
|
}, [currentTabItems, activeFilter, searchQuery]);
|
||||||
|
|
||||||
// Count unread items per tab
|
// Count unread items per tab (filter-aware)
|
||||||
const unreadMentionsCount = useMemo(
|
const unreadMentionsCount = useMemo(() => {
|
||||||
() => mentionItems.filter((item) => !item.read).length,
|
if (activeFilter === "archived") {
|
||||||
[mentionItems]
|
// In archived view, show unread archived items
|
||||||
);
|
return mentionItems.filter((item) => !item.read && item.archived === true).length;
|
||||||
|
}
|
||||||
|
// For "all" and "unread" filters, show unread non-archived items
|
||||||
|
return mentionItems.filter((item) => !item.read && item.archived !== true).length;
|
||||||
|
}, [mentionItems, activeFilter]);
|
||||||
|
|
||||||
const unreadStatusCount = useMemo(
|
const unreadStatusCount = useMemo(() => {
|
||||||
() => statusItems.filter((item) => !item.read).length,
|
if (activeFilter === "archived") {
|
||||||
[statusItems]
|
// In archived view, show unread archived items
|
||||||
);
|
return statusItems.filter((item) => !item.read && item.archived === true).length;
|
||||||
|
}
|
||||||
|
// For "all" and "unread" filters, show unread non-archived items
|
||||||
|
return statusItems.filter((item) => !item.read && item.archived !== true).length;
|
||||||
|
}, [statusItems, activeFilter]);
|
||||||
|
|
||||||
const handleItemClick = useCallback(
|
const handleItemClick = useCallback(
|
||||||
async (item: InboxItem) => {
|
async (item: InboxItem) => {
|
||||||
|
|
@ -283,7 +291,7 @@ export function InboxSidebar({
|
||||||
case "in_progress":
|
case "in_progress":
|
||||||
return (
|
return (
|
||||||
<div className="h-8 w-8 flex items-center justify-center rounded-full bg-muted">
|
<div className="h-8 w-8 flex items-center justify-center rounded-full bg-muted">
|
||||||
<Loader2 className="h-4 w-4 text-foreground animate-spin" />
|
<Spinner size="sm" className="text-foreground" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case "completed":
|
case "completed":
|
||||||
|
|
@ -363,7 +371,7 @@ export function InboxSidebar({
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8 rounded-full"
|
className="h-8 w-8 rounded-full"
|
||||||
>
|
>
|
||||||
<ListFilter className="h-4 w-4" />
|
<ListFilter className="h-4 w-4 text-muted-foreground" />
|
||||||
<span className="sr-only">{t("filter") || "Filter"}</span>
|
<span className="sr-only">{t("filter") || "Filter"}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
@ -413,7 +421,7 @@ export function InboxSidebar({
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8 rounded-full"
|
className="h-8 w-8 rounded-full"
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontal className="h-4 w-4 text-muted-foreground" />
|
||||||
<span className="sr-only">{t("more_options") || "More options"}</span>
|
<span className="sr-only">{t("more_options") || "More options"}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
@ -466,11 +474,12 @@ export function InboxSidebar({
|
||||||
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
||||||
<AtSign className="h-4 w-4" />
|
<AtSign className="h-4 w-4" />
|
||||||
<span>{t("mentions") || "Mentions"}</span>
|
<span>{t("mentions") || "Mentions"}</span>
|
||||||
{unreadMentionsCount > 0 && (
|
<span className={cn(
|
||||||
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-primary text-xs font-medium">
|
"inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-muted-foreground text-xs font-medium",
|
||||||
{unreadMentionsCount}
|
unreadMentionsCount === 0 && "invisible"
|
||||||
</span>
|
)}>
|
||||||
)}
|
{unreadMentionsCount || 0}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
|
|
@ -480,11 +489,12 @@ export function InboxSidebar({
|
||||||
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
<span className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors">
|
||||||
<History className="h-4 w-4" />
|
<History className="h-4 w-4" />
|
||||||
<span>{t("status") || "Status"}</span>
|
<span>{t("status") || "Status"}</span>
|
||||||
{unreadStatusCount > 0 && (
|
<span className={cn(
|
||||||
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-primary text-xs font-medium">
|
"inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-muted-foreground text-xs font-medium",
|
||||||
{unreadStatusCount}
|
unreadStatusCount === 0 && "invisible"
|
||||||
</span>
|
)}>
|
||||||
)}
|
{unreadStatusCount || 0}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
@ -493,7 +503,7 @@ export function InboxSidebar({
|
||||||
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
|
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex items-center justify-center py-8">
|
<div className="flex items-center justify-center py-8">
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
<Spinner size="md" className="text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
) : filteredItems.length > 0 ? (
|
) : filteredItems.length > 0 ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -507,7 +517,7 @@ export function InboxSidebar({
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center gap-3 rounded-lg px-3 py-5 text-sm",
|
"group flex items-center gap-3 rounded-lg px-3 py-2 text-sm h-[72px] overflow-hidden",
|
||||||
"hover:bg-accent hover:text-accent-foreground",
|
"hover:bg-accent hover:text-accent-foreground",
|
||||||
"transition-colors cursor-pointer",
|
"transition-colors cursor-pointer",
|
||||||
isBusy && "opacity-50 pointer-events-none"
|
isBusy && "opacity-50 pointer-events-none"
|
||||||
|
|
@ -547,11 +557,7 @@ export function InboxSidebar({
|
||||||
}}
|
}}
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
>
|
>
|
||||||
{isMarkingAsRead ? (
|
<CheckCheck className="h-3.5 w-3.5" />
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<CheckCheck className="h-3.5 w-3.5" />
|
|
||||||
)}
|
|
||||||
<span className="sr-only">{t("mark_as_read") || "Mark as read"}</span>
|
<span className="sr-only">{t("mark_as_read") || "Mark as read"}</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
@ -566,7 +572,7 @@ export function InboxSidebar({
|
||||||
disabled={isArchiving}
|
disabled={isArchiving}
|
||||||
>
|
>
|
||||||
{isArchiving ? (
|
{isArchiving ? (
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
<Spinner size="xs" />
|
||||||
) : isArchived ? (
|
) : isArchived ? (
|
||||||
<RotateCcw className="h-3.5 w-3.5" />
|
<RotateCcw className="h-3.5 w-3.5" />
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -623,10 +629,10 @@ export function InboxSidebar({
|
||||||
)}
|
)}
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
>
|
>
|
||||||
{isBusy ? (
|
{isArchiving ? (
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
<Spinner size="xs" />
|
||||||
) : (
|
) : (
|
||||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
{t("more_options") || "More options"}
|
{t("more_options") || "More options"}
|
||||||
|
|
|
||||||
35
surfsense_web/components/ui/spinner.tsx
Normal file
35
surfsense_web/components/ui/spinner.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface SpinnerProps {
|
||||||
|
/** Size of the spinner */
|
||||||
|
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
||||||
|
/** Whether to hide the track behind the spinner arc */
|
||||||
|
hideTrack?: boolean;
|
||||||
|
/** Additional classes to apply */
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
xs: "h-3 w-3 border-[1.5px]",
|
||||||
|
sm: "h-4 w-4 border-2",
|
||||||
|
md: "h-6 w-6 border-2",
|
||||||
|
lg: "h-8 w-8 border-[3px]",
|
||||||
|
xl: "h-10 w-10 border-4",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Spinner({ size = "md", hideTrack = false, className }: SpinnerProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="status"
|
||||||
|
aria-label="Loading"
|
||||||
|
className={cn(
|
||||||
|
"animate-spin rounded-full",
|
||||||
|
hideTrack ? "border-transparent" : "border-current/20",
|
||||||
|
"border-t-current",
|
||||||
|
sizeClasses[size],
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue