### What problem does this PR solve? Feat: Add LoadingButton #4368 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.16.0
| // https://github.com/hsuanyi-chou/shadcn-ui-expansions/blob/main/components/ui/loading-button.tsx | |||||
| import { cn } from '@/lib/utils'; | |||||
| import { Slot, Slottable } from '@radix-ui/react-slot'; | |||||
| import { cva, type VariantProps } from 'class-variance-authority'; | |||||
| import { Loader2 } from 'lucide-react'; | |||||
| import * as React from 'react'; | |||||
| const buttonVariants = cva( | |||||
| 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', | |||||
| { | |||||
| variants: { | |||||
| variant: { | |||||
| default: 'bg-primary text-primary-foreground hover:bg-primary/90', | |||||
| destructive: | |||||
| 'bg-destructive text-destructive-foreground hover:bg-destructive/90', | |||||
| outline: | |||||
| 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', | |||||
| secondary: | |||||
| 'bg-secondary text-secondary-foreground hover:bg-secondary/80', | |||||
| ghost: 'hover:bg-accent hover:text-accent-foreground', | |||||
| link: 'text-primary underline-offset-4 hover:underline', | |||||
| }, | |||||
| size: { | |||||
| default: 'h-10 px-4 py-2', | |||||
| sm: 'h-9 rounded-md px-3', | |||||
| lg: 'h-11 rounded-md px-8', | |||||
| icon: 'h-10 w-10', | |||||
| }, | |||||
| }, | |||||
| defaultVariants: { | |||||
| variant: 'default', | |||||
| size: 'default', | |||||
| }, | |||||
| }, | |||||
| ); | |||||
| export interface ButtonProps | |||||
| extends React.ButtonHTMLAttributes<HTMLButtonElement>, | |||||
| VariantProps<typeof buttonVariants> { | |||||
| asChild?: boolean; | |||||
| loading?: boolean; | |||||
| } | |||||
| const LoadingButton = React.forwardRef<HTMLButtonElement, ButtonProps>( | |||||
| ( | |||||
| { | |||||
| className, | |||||
| loading = false, | |||||
| children, | |||||
| disabled, | |||||
| variant, | |||||
| size, | |||||
| asChild = false, | |||||
| ...props | |||||
| }, | |||||
| ref, | |||||
| ) => { | |||||
| const Comp = asChild ? Slot : 'button'; | |||||
| return ( | |||||
| <Comp | |||||
| className={cn(buttonVariants({ variant, size, className }))} | |||||
| ref={ref} | |||||
| disabled={loading || disabled} | |||||
| {...props} | |||||
| > | |||||
| {loading && <Loader2 className="mr-2 h-5 w-5 animate-spin" />} | |||||
| <Slottable>{children}</Slottable> | |||||
| </Comp> | |||||
| ); | |||||
| }, | |||||
| ); | |||||
| LoadingButton.displayName = 'LoadingButton'; | |||||
| export { LoadingButton, buttonVariants }; |
| isPending: loading, | isPending: loading, | ||||
| mutateAsync, | mutateAsync, | ||||
| } = useMutation({ | } = useMutation({ | ||||
| mutationKey: ['deleteTag'], | |||||
| mutationKey: ['renameTag'], | |||||
| mutationFn: async (params: IRenameTag) => { | mutationFn: async (params: IRenameTag) => { | ||||
| const { data } = await renameTag(knowledgeBaseId, params); | const { data } = await renameTag(knowledgeBaseId, params); | ||||
| if (data.code === 0) { | if (data.code === 0) { | ||||
| return { data, loading, renameTag: mutateAsync }; | return { data, loading, renameTag: mutateAsync }; | ||||
| }; | }; | ||||
| export const useTagIsRenaming = () => { | |||||
| return useIsMutating({ mutationKey: ['renameTag'] }) > 0; | |||||
| }; | |||||
| //#endregion | //#endregion |
| import { useSetModalState } from '@/hooks/common-hooks'; | import { useSetModalState } from '@/hooks/common-hooks'; | ||||
| import { | import { | ||||
| useFetchKnowledgeBaseConfiguration, | useFetchKnowledgeBaseConfiguration, | ||||
| useRenameTag, | |||||
| useUpdateKnowledge, | useUpdateKnowledge, | ||||
| } from '@/hooks/knowledge-hooks'; | } from '@/hooks/knowledge-hooks'; | ||||
| import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks'; | import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks'; | ||||
| hideModal: hideTagRenameModal, | hideModal: hideTagRenameModal, | ||||
| showModal: showFileRenameModal, | showModal: showFileRenameModal, | ||||
| } = useSetModalState(); | } = useSetModalState(); | ||||
| const { renameTag, loading } = useRenameTag(); | |||||
| const onTagRenameOk = useCallback( | |||||
| async (name: string) => { | |||||
| const ret = await renameTag({ | |||||
| fromTag: tag, | |||||
| toTag: name, | |||||
| }); | |||||
| if (ret === 0) { | |||||
| hideTagRenameModal(); | |||||
| } | |||||
| }, | |||||
| [renameTag, tag, hideTagRenameModal], | |||||
| ); | |||||
| const handleShowTagRenameModal = useCallback( | const handleShowTagRenameModal = useCallback( | ||||
| (record: string) => { | (record: string) => { | ||||
| ); | ); | ||||
| return { | return { | ||||
| renameLoading: loading, | |||||
| initialName: tag, | initialName: tag, | ||||
| onTagRenameOk, | |||||
| tagRenameVisible, | tagRenameVisible, | ||||
| hideTagRenameModal, | hideTagRenameModal, | ||||
| showTagRenameModal: handleShowTagRenameModal, | showTagRenameModal: handleShowTagRenameModal, |
| showTagRenameModal, | showTagRenameModal, | ||||
| hideTagRenameModal, | hideTagRenameModal, | ||||
| tagRenameVisible, | tagRenameVisible, | ||||
| onTagRenameOk, | |||||
| initialName, | initialName, | ||||
| } = useRenameKnowledgeTag(); | } = useRenameKnowledgeTag(); | ||||
| {tagRenameVisible && ( | {tagRenameVisible && ( | ||||
| <RenameDialog | <RenameDialog | ||||
| hideModal={hideTagRenameModal} | hideModal={hideTagRenameModal} | ||||
| onOk={onTagRenameOk} | |||||
| initialName={initialName} | initialName={initialName} | ||||
| ></RenameDialog> | ></RenameDialog> | ||||
| )} | )} |
| import { Button } from '@/components/ui/button'; | |||||
| import { | import { | ||||
| Dialog, | Dialog, | ||||
| DialogContent, | DialogContent, | ||||
| DialogHeader, | DialogHeader, | ||||
| DialogTitle, | DialogTitle, | ||||
| } from '@/components/ui/dialog'; | } from '@/components/ui/dialog'; | ||||
| import { LoadingButton } from '@/components/ui/loading-button'; | |||||
| import { useTagIsRenaming } from '@/hooks/knowledge-hooks'; | |||||
| import { IModalProps } from '@/interfaces/common'; | import { IModalProps } from '@/interfaces/common'; | ||||
| import { TagRenameId } from '@/pages/add-knowledge/constant'; | import { TagRenameId } from '@/pages/add-knowledge/constant'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| initialName, | initialName, | ||||
| }: IModalProps<any> & { initialName: string }) { | }: IModalProps<any> & { initialName: string }) { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const loading = useTagIsRenaming(); | |||||
| return ( | return ( | ||||
| <Dialog open onOpenChange={hideModal}> | <Dialog open onOpenChange={hideModal}> | ||||
| hideModal={hideModal} | hideModal={hideModal} | ||||
| ></RenameForm> | ></RenameForm> | ||||
| <DialogFooter> | <DialogFooter> | ||||
| <Button type="submit" form={TagRenameId}> | |||||
| <LoadingButton type="submit" form={TagRenameId} loading={loading}> | |||||
| {t('common.save')} | {t('common.save')} | ||||
| </Button> | |||||
| </LoadingButton> | |||||
| </DialogFooter> | </DialogFooter> | ||||
| </DialogContent> | </DialogContent> | ||||
| </Dialog> | </Dialog> |