From e56edbaa4fc0c45fd5482ac960feb35a8e8d0e6a Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Mon, 3 Nov 2025 16:30:45 +0800 Subject: [PATCH] feat: enhance Button component with loading state and custom loading icon - Added loading state support to the Button component, allowing for a spinner to be displayed when the button is in a loading state. - Introduced props for custom loading icons and adjusted button behavior to disable when loading. - Updated various components to utilize the new loading feature for better user experience during asynchronous actions. --- .../ui/src/components/primitives/button.tsx | 35 ++++++++++++++++++- .../Avatar/EmojiAvatarWithPicker.tsx | 7 ++-- .../Popups/ExportToPhoneLanPopup.tsx | 10 +++--- src/renderer/src/components/UpdateDialog.tsx | 6 ++-- .../Tools/ToolPermissionRequestCard.tsx | 35 ++++++++++--------- .../pages/home/Tabs/SessionSettingsTab.tsx | 5 +-- .../AgentSettings/AccessibleDirsSetting.tsx | 9 +++-- .../components/InstalledPluginsList.tsx | 16 ++++----- .../components/PluginBrowser.tsx | 9 +++-- .../AgentSettings/components/PluginCard.tsx | 21 ++++++----- 10 files changed, 96 insertions(+), 57 deletions(-) diff --git a/packages/ui/src/components/primitives/button.tsx b/packages/ui/src/components/primitives/button.tsx index 9621e26b42..14eff25723 100644 --- a/packages/ui/src/components/primitives/button.tsx +++ b/packages/ui/src/components/primitives/button.tsx @@ -1,6 +1,7 @@ import { cn } from '@cherrystudio/ui/utils/index' import { Slot } from '@radix-ui/react-slot' import { cva, type VariantProps } from 'class-variance-authority' +import { Loader } from 'lucide-react' import * as React from 'react' const buttonVariants = cva( @@ -38,14 +39,46 @@ function Button({ variant, size, asChild = false, + loading = false, + loadingIcon, + loadingIconClassName, + disabled, + children, ...props }: React.ComponentProps<'button'> & VariantProps & { asChild?: boolean + loading?: boolean + loadingIcon?: React.ReactNode + loadingIconClassName?: string }) { const Comp = asChild ? Slot : 'button' - return + // 根据按钮尺寸确定 spinner 大小 + const getSpinnerSize = () => { + if (size === 'sm' || size === 'icon-sm') return 14 + if (size === 'lg' || size === 'icon-lg') return 18 + return 16 + } + + // 默认 loading icon + const defaultLoadingIcon = ( + + ) + + // 使用自定义 icon 或默认 icon + const spinnerElement = loadingIcon ?? defaultLoadingIcon + + return ( + + {loading && spinnerElement} + {children} + + ) } export { Button, buttonVariants } diff --git a/src/renderer/src/components/Avatar/EmojiAvatarWithPicker.tsx b/src/renderer/src/components/Avatar/EmojiAvatarWithPicker.tsx index 6735d86a4e..a1b8b6ce4a 100644 --- a/src/renderer/src/components/Avatar/EmojiAvatarWithPicker.tsx +++ b/src/renderer/src/components/Avatar/EmojiAvatarWithPicker.tsx @@ -1,4 +1,5 @@ -import { Button, Popover, PopoverContent, PopoverTrigger } from '@heroui/react' +import { Button } from '@cherrystudio/ui' +import { Popover, PopoverContent, PopoverTrigger } from '@heroui/react' import React from 'react' import EmojiPicker from '../EmojiPicker' @@ -12,7 +13,9 @@ export const EmojiAvatarWithPicker: React.FC = ({ emoji, onPick }) => { return ( - diff --git a/src/renderer/src/components/Popups/ExportToPhoneLanPopup.tsx b/src/renderer/src/components/Popups/ExportToPhoneLanPopup.tsx index 1920d0b210..c758269bf0 100644 --- a/src/renderer/src/components/Popups/ExportToPhoneLanPopup.tsx +++ b/src/renderer/src/components/Popups/ExportToPhoneLanPopup.tsx @@ -1,4 +1,4 @@ -import { Button } from '@heroui/button' +import { Button } from '@cherrystudio/ui' import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@heroui/modal' import { Progress } from '@heroui/progress' import { Spinner } from '@heroui/spinner' @@ -517,10 +517,10 @@ const PopupContainer: React.FC = ({ resolve }) => {
- -
@@ -564,10 +564,10 @@ const PopupContainer: React.FC = ({ resolve }) => { {t('settings.data.export_to_phone.lan.confirm_close_message')}
- -
diff --git a/src/renderer/src/components/UpdateDialog.tsx b/src/renderer/src/components/UpdateDialog.tsx index 9cb9b5b62b..93729b0a39 100644 --- a/src/renderer/src/components/UpdateDialog.tsx +++ b/src/renderer/src/components/UpdateDialog.tsx @@ -1,4 +1,5 @@ -import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ScrollShadow } from '@heroui/react' +import { Button } from '@cherrystudio/ui' +import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ScrollShadow } from '@heroui/react' import { loggerService } from '@logger' import { handleSaveData } from '@renderer/store' import type { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime' @@ -82,12 +83,11 @@ const UpdateDialog: React.FC = ({ isOpen, onClose, releaseInf diff --git a/src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx b/src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx index e2678357ca..2e014cb745 100644 --- a/src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx @@ -1,5 +1,6 @@ import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk' -import { Button, Chip, ScrollShadow } from '@heroui/react' +import { Button } from '@cherrystudio/ui' +import { Chip, ScrollShadow } from '@heroui/react' import { loggerService } from '@logger' import { useAppDispatch, useAppSelector } from '@renderer/store' import { selectPendingPermissionByToolName, toolPermissionsActions } from '@renderer/store/toolPermissions' @@ -137,23 +138,23 @@ export function ToolPermissionRequestCard({ toolResponse }: Props) { @@ -161,10 +162,10 @@ export function ToolPermissionRequestCard({ toolResponse }: Props) { aria-label={ showDetails ? t('agent.toolPermission.aria.hideDetails') : t('agent.toolPermission.aria.showDetails') } - className="h-8" - isIconOnly - onPress={() => setShowDetails((value) => !value)} - variant="light"> + size="icon" + className="h-8 w-8" + onClick={() => setShowDetails((value) => !value)} + variant="ghost"> diff --git a/src/renderer/src/pages/home/Tabs/SessionSettingsTab.tsx b/src/renderer/src/pages/home/Tabs/SessionSettingsTab.tsx index 53a2e354d4..effc03f8db 100644 --- a/src/renderer/src/pages/home/Tabs/SessionSettingsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/SessionSettingsTab.tsx @@ -1,4 +1,5 @@ -import { Button, Divider } from '@heroui/react' +import { Button } from '@cherrystudio/ui' +import { Divider } from '@heroui/react' import type { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import { SessionSettingsPopup } from '@renderer/pages/settings/AgentSettings' import AdvancedSettings from '@renderer/pages/settings/AgentSettings/AdvancedSettings' @@ -33,7 +34,7 @@ const SessionSettingsTab: FC = ({ session, update }) => { - diff --git a/src/renderer/src/pages/settings/AgentSettings/AccessibleDirsSetting.tsx b/src/renderer/src/pages/settings/AgentSettings/AccessibleDirsSetting.tsx index 53bc6ed73d..b4e7009b3e 100644 --- a/src/renderer/src/pages/settings/AgentSettings/AccessibleDirsSetting.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/AccessibleDirsSetting.tsx @@ -1,4 +1,5 @@ -import { Button, Tooltip } from '@heroui/react' +import { Button } from '@cherrystudio/ui' +import { Tooltip } from '@heroui/react' import { loggerService } from '@logger' import type { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types' import { Plus } from 'lucide-react' @@ -66,7 +67,9 @@ export const AccessibleDirsSetting = ({ base, update }: AccessibleDirsSettingPro - }> {t('agent.session.accessible_paths.label')} @@ -79,7 +82,7 @@ export const AccessibleDirsSetting = ({ base, update }: AccessibleDirsSettingPro {path} - diff --git a/src/renderer/src/pages/settings/AgentSettings/components/InstalledPluginsList.tsx b/src/renderer/src/pages/settings/AgentSettings/components/InstalledPluginsList.tsx index f1578f10af..525aac4deb 100644 --- a/src/renderer/src/pages/settings/AgentSettings/components/InstalledPluginsList.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/components/InstalledPluginsList.tsx @@ -1,4 +1,5 @@ -import { Button, Chip, Skeleton, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@heroui/react' +import { Button } from '@cherrystudio/ui' +import { Chip, Skeleton, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@heroui/react' import type { InstalledPlugin } from '@renderer/types/plugin' import { Trash2 } from 'lucide-react' import type { FC } from 'react' @@ -81,13 +82,12 @@ export const InstalledPluginsList: FC = ({ plugins, o diff --git a/src/renderer/src/pages/settings/AgentSettings/components/PluginBrowser.tsx b/src/renderer/src/pages/settings/AgentSettings/components/PluginBrowser.tsx index e84ef5108f..014948072e 100644 --- a/src/renderer/src/pages/settings/AgentSettings/components/PluginBrowser.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/components/PluginBrowser.tsx @@ -1,4 +1,5 @@ -import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Tab, Tabs } from '@heroui/react' +import { Button } from '@cherrystudio/ui' +import { Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Tab, Tabs } from '@heroui/react' import type { InstalledPlugin, PluginMetadata } from '@renderer/types/plugin' import { Filter, Search } from 'lucide-react' import type { FC } from 'react' @@ -185,10 +186,8 @@ export const PluginBrowser: FC = ({ diff --git a/src/renderer/src/pages/settings/AgentSettings/components/PluginCard.tsx b/src/renderer/src/pages/settings/AgentSettings/components/PluginCard.tsx index bf14c25e23..7b66ebaa53 100644 --- a/src/renderer/src/pages/settings/AgentSettings/components/PluginCard.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/components/PluginCard.tsx @@ -1,4 +1,5 @@ -import { Button, Card, CardBody, CardFooter, CardHeader, Chip, Spinner } from '@heroui/react' +import { Button } from '@cherrystudio/ui' +import { Card, CardBody, CardFooter, CardHeader, Chip, Spinner } from '@heroui/react' import type { PluginMetadata } from '@renderer/types/plugin' import { upperFirst } from 'lodash' import { Download, Trash2 } from 'lucide-react' @@ -56,30 +57,28 @@ export const PluginCard: FC = ({ plugin, installed, onInstall, {installed ? ( ) : ( )}