ui icon indicating copy to clipboard operation
ui copied to clipboard

feat: Add loading state to the Button

Open malakhov-dmitrii opened this issue 2 years ago • 5 comments

This PR adds simple handling for the loading state: the original content becomes invisible to ensure that the element's width is not changed; the loading indicator is shown instead of the original content with pulse animation. Later on it other handlers can be added, such as custom loading indicator/icon/text.

malakhov-dmitrii avatar Feb 17 '23 04:02 malakhov-dmitrii

@malakhov-dmitrii is attempting to deploy a commit to the shadcn-pro Team on Vercel.

A member of the Team first needs to authorize it.

vercel[bot] avatar Feb 17 '23 04:02 vercel[bot]

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated
next-template ❌ Failed (Inspect) Feb 17, 2023 at 4:15AM (UTC)

vercel[bot] avatar Feb 17 '23 04:02 vercel[bot]

I think it's useful cause the button loading state it's common practice, but I want to simplify the code structure a bit, instead of adding a loading CVA variant, I think more propper just disable the Button and already defined disabled styles will be applied to the button when loading prop is true. You can see an example of creating your own button with a loading state in docs here. So I suggest to refactor that to smth like this:

button.tsx
index 088678c..52fe801 100644
--- a/templates/next-template/components/ui/button.tsx
+++ b/templates/next-template/components/ui/button.tsx
@@ -1,9 +1,8 @@
 import * as React from "react"
-import type { VariantProps } from "class-variance-authority"
-import { cva } from "class-variance-authority"
+import { VariantProps, cva } from "class-variance-authority"
+import { Loader2 } from "lucide-react"
 
 import { cn } from "@/lib/utils"
-import { Loader2 } from "lucide-react"

 const buttonVariants = cva(
   "active:scale-95 inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800",
@@ -27,10 +26,6 @@ const buttonVariants = cva(
         sm: "h-9 px-2 rounded-md",
         lg: "h-11 px-8 rounded-md",
       },
-
-      modifiers: {
-        loading: "opacity-50 animate-pulse pointer-events-none",
-      },
     },
     defaultVariants: {
       variant: "default",
@@ -46,23 +41,24 @@ export interface ButtonProps
 }

 const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
-  ({ className, variant, size, loading, children, ...props }, ref) => {
+  (
+    { className, variant, size, loading, disabled, children, ...props },
+    ref
+  ) => {
     return (
       <button
         className={cn(
           buttonVariants({
             variant,
             size,
-            ...(loading && { modifiers: "loading" }),
             className,
           })
         )}
+        disabled={loading || disabled}
         ref={ref}
         {...props}
       >
-        {!loading && children}
-        {/* Maintain same width of element */}
-        {loading && <LoadingIndicator>{children}</LoadingIndicator>}
+        {loading ? <LoadingIndicator>{children}</LoadingIndicator> : children}
       </button>
     )
   }
@@ -74,10 +70,10 @@ export { Button, buttonVariants }
 const LoadingIndicator = ({ children }: { children: React.ReactNode }) => {
   return (
     <div className="relative flex items-center justify-center">
-      <div className="absolute top-0 left-0 flex h-full w-full transform items-center justify-center">
-        <Loader2 className="animate-spin animate-pulse" />
+      <div className="absolute top-0 left-0 flex h-full w-full items-center justify-center">
+        <Loader2 className="animate-spin" />
       </div>
       <div className="invisible">{children}</div>
     </div>
   )
-}
+}

its-monotype avatar Feb 17 '23 12:02 its-monotype

And also I think a good idea would be to add a loading text. You can pass it through loadingText prop and when loading is true display loader + if loadingText is exists display it or if there is no loadingText display invisible button content.

its-monotype avatar Feb 17 '23 12:02 its-monotype

actually I've made this weeks ago... https://github.com/shadcn/ui/pull/63

Ding-Fan avatar Feb 20 '23 07:02 Ding-Fan

@malakhov-dmitrii I like this but let's go with @its-monotype implementation which is simpler. I'll create a PR for it.

shadcn avatar Oct 15 '23 11:10 shadcn