mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-10 20:35:17 +02:00
refactor: enhance button loading states in various components for improved user experience
This commit is contained in:
parent
75fd39c249
commit
fec5c005eb
6 changed files with 42 additions and 91 deletions
|
|
@ -986,9 +986,10 @@ export function DocumentsTableShell({
|
|||
handleDeleteFromMenu();
|
||||
}}
|
||||
disabled={isDeleting}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isDeleting ? <Spinner size="sm" /> : "Delete"}
|
||||
<span className={isDeleting ? "opacity-0" : ""}>Delete</span>
|
||||
{isDeleting && <Spinner size="sm" className="absolute" />}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
|
@ -1104,9 +1105,10 @@ export function DocumentsTableShell({
|
|||
handleBulkDelete();
|
||||
}}
|
||||
disabled={isBulkDeleting}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isBulkDeleting ? <Spinner size="sm" /> : "Delete"}
|
||||
<span className={isBulkDeleting ? "opacity-0" : ""}>Delete</span>
|
||||
{isBulkDeleting && <Spinner size="sm" className="absolute" />}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
|
|
|||
|
|
@ -165,8 +165,9 @@ export function PromptsContent() {
|
|||
<Button variant="ghost" size="sm" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? <Spinner className="size-3.5" /> : editingId ? "Update" : "Create"}
|
||||
<Button size="sm" onClick={handleSave} disabled={isSaving} className="relative">
|
||||
<span className={isSaving ? "opacity-0" : ""}>{editingId ? "Update" : "Create"}</span>
|
||||
{isSaving && <Spinner className="size-3.5 absolute" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { AlertTriangle, Cable, Settings } from "lucide-react";
|
||||
import { AlertTriangle, Settings } from "lucide-react";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { statusInboxItemsAtom } from "@/atoms/inbox/status-inbox.atom";
|
||||
|
|
@ -12,17 +12,14 @@ import {
|
|||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
import { searchSpaceSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms";
|
||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Tabs, TabsContent } from "@/components/ui/tabs";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { useConnectorsSync } from "@/hooks/use-connectors-sync";
|
||||
import { PICKER_CLOSE_EVENT, PICKER_OPEN_EVENT } from "@/hooks/use-google-picker";
|
||||
import { useZeroDocumentTypeCounts } from "@/hooks/use-zero-document-type-counts";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ConnectorDialogHeader } from "./connector-popup/components/connector-dialog-header";
|
||||
import { ConnectorConnectView } from "./connector-popup/connector-configs/views/connector-connect-view";
|
||||
import { ConnectorEditView } from "./connector-popup/connector-configs/views/connector-edit-view";
|
||||
|
|
@ -47,7 +44,7 @@ interface ConnectorIndicatorProps {
|
|||
}
|
||||
|
||||
export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, ConnectorIndicatorProps>(
|
||||
({ showTrigger = true }, ref) => {
|
||||
(_props, ref) => {
|
||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||
const setSearchSpaceSettingsDialog = useSetAtom(searchSpaceSettingsDialogAtom);
|
||||
useAtomValue(currentUserAtom);
|
||||
|
|
@ -74,8 +71,6 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
|
||||
// Real-time document type counts via Zero (updates instantly as docs are indexed)
|
||||
const documentTypeCounts = useZeroDocumentTypeCounts(searchSpaceId);
|
||||
const documentTypesLoading = documentTypeCounts === undefined;
|
||||
|
||||
// Read status inbox items from shared atom (populated by LayoutDataProvider)
|
||||
// instead of creating a duplicate useInbox("status") hook.
|
||||
const statusInboxItems = useAtomValue(statusInboxItemsAtom);
|
||||
|
|
@ -178,8 +173,6 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
inboxItems
|
||||
);
|
||||
|
||||
const isLoading = connectorsLoading || documentTypesLoading;
|
||||
|
||||
// Get document types that have documents in the search space
|
||||
const activeDocumentTypes = documentTypeCounts
|
||||
? Object.entries(documentTypeCounts).filter(([, count]) => count > 0)
|
||||
|
|
@ -205,40 +198,6 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
|
||||
return (
|
||||
<Dialog open={isOpen} modal={false} onOpenChange={handleOpenChange}>
|
||||
{showTrigger && (
|
||||
<TooltipIconButton
|
||||
data-joyride="connector-icon"
|
||||
tooltip={
|
||||
hasConnectors ? `Manage ${activeConnectorsCount} connectors` : "Connect your data"
|
||||
}
|
||||
side="bottom"
|
||||
className={cn(
|
||||
"size-[34px] rounded-full p-1 flex items-center justify-center transition-colors relative",
|
||||
"hover:bg-muted-foreground/15 dark:hover:bg-muted-foreground/30",
|
||||
"outline-none focus:outline-none focus-visible:outline-none font-semibold text-xs",
|
||||
"border-0 ring-0 focus:ring-0 shadow-none focus:shadow-none"
|
||||
)}
|
||||
aria-label={
|
||||
hasConnectors
|
||||
? `View ${activeConnectorsCount} connectors`
|
||||
: "Add your first connector"
|
||||
}
|
||||
onClick={() => handleOpenChange(true)}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Spinner size="sm" />
|
||||
) : (
|
||||
<>
|
||||
<Cable className="size-4 stroke-[1.5px]" />
|
||||
{activeConnectorsCount > 0 && (
|
||||
<span className="absolute -top-0.5 right-0 flex items-center justify-center min-w-[16px] h-4 px-1 text-[10px] font-medium rounded-full bg-primary text-primary-foreground shadow-sm select-none">
|
||||
{activeConnectorsCount > 99 ? "99+" : activeConnectorsCount}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</TooltipIconButton>
|
||||
)}
|
||||
|
||||
{isOpen &&
|
||||
createPortal(
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
|||
size="sm"
|
||||
variant={isConnected ? "secondary" : "default"}
|
||||
className={cn(
|
||||
"h-8 text-[11px] px-3 rounded-lg shrink-0 font-medium",
|
||||
"relative h-8 text-[11px] px-3 rounded-lg shrink-0 font-medium items-center justify-center",
|
||||
isConnected &&
|
||||
"bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80",
|
||||
!isConnected && "shadow-xs"
|
||||
|
|
@ -151,19 +151,18 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
|||
onClick={isConnected ? onManage : onConnect}
|
||||
disabled={isConnecting || !isEnabled}
|
||||
>
|
||||
{isConnecting ? (
|
||||
<Spinner size="xs" />
|
||||
) : !isEnabled ? (
|
||||
"Unavailable"
|
||||
) : isConnected ? (
|
||||
"Manage"
|
||||
) : id === "youtube-crawler" ? (
|
||||
"Add"
|
||||
) : connectorType ? (
|
||||
"Connect"
|
||||
) : (
|
||||
"Add"
|
||||
)}
|
||||
<span className={isConnecting ? "opacity-0" : ""}>
|
||||
{!isEnabled
|
||||
? "Unavailable"
|
||||
: isConnected
|
||||
? "Manage"
|
||||
: id === "youtube-crawler"
|
||||
? "Add"
|
||||
: connectorType
|
||||
? "Connect"
|
||||
: "Add"}
|
||||
</span>
|
||||
{isConnecting && <Spinner size="xs" className="absolute" />}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -795,9 +795,10 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
confirmDeleteChat();
|
||||
}}
|
||||
disabled={isDeletingChat}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 gap-2"
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90 items-center justify-center"
|
||||
>
|
||||
{isDeletingChat ? <Spinner size="sm" /> : tCommon("delete")}
|
||||
<span className={isDeletingChat ? "opacity-0" : ""}>{tCommon("delete")}</span>
|
||||
{isDeletingChat && <Spinner size="sm" className="absolute" />}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
|
@ -835,15 +836,11 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
<Button
|
||||
onClick={confirmRenameChat}
|
||||
disabled={isRenamingChat || !newChatTitle.trim()}
|
||||
className="gap-2"
|
||||
className="relative"
|
||||
>
|
||||
{isRenamingChat ? (
|
||||
<>
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
{tSidebar("renaming") || "Renaming"}
|
||||
</>
|
||||
) : (
|
||||
tSidebar("rename") || "Rename"
|
||||
<span className={isRenamingChat ? "opacity-0" : ""}>{tSidebar("rename") || "Rename"}</span>
|
||||
{isRenamingChat && (
|
||||
<span className="absolute h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
@ -869,15 +866,11 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
confirmDeleteSearchSpace();
|
||||
}}
|
||||
disabled={isDeletingSearchSpace}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 gap-2"
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isDeletingSearchSpace ? (
|
||||
<>
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
{t("deleting")}
|
||||
</>
|
||||
) : (
|
||||
tCommon("delete")
|
||||
<span className={isDeletingSearchSpace ? "opacity-0" : ""}>{tCommon("delete")}</span>
|
||||
{isDeletingSearchSpace && (
|
||||
<span className="absolute h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
|
|
@ -903,15 +896,11 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
confirmLeaveSearchSpace();
|
||||
}}
|
||||
disabled={isLeavingSearchSpace}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 gap-2"
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isLeavingSearchSpace ? (
|
||||
<>
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
{t("leaving")}
|
||||
</>
|
||||
) : (
|
||||
t("leave")
|
||||
<span className={isLeavingSearchSpace ? "opacity-0" : ""}>{t("leave")}</span>
|
||||
{isLeavingSearchSpace && (
|
||||
<span className="absolute h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
|
|
|
|||
|
|
@ -807,9 +807,10 @@ export function DocumentsSidebar({
|
|||
handleBulkDeleteSelected();
|
||||
}}
|
||||
disabled={isBulkDeleting}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isBulkDeleting ? <Spinner size="sm" /> : "Delete"}
|
||||
<span className={isBulkDeleting ? "opacity-0" : ""}>Delete</span>
|
||||
{isBulkDeleting && <Spinner size="sm" className="absolute" />}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue