Просмотр исходного кода

refactor: add custom button component

Make custom button component.
- add loading logic into button
- add left/right section in button
satnaing 2 лет назад
Родитель
Сommit
cdb3172f9a
28 измененных файлов с 127 добавлено и 130 удалено
  1. 88 0
      src/components/custom/button.tsx
  2. 1 1
      src/components/nav.tsx
  3. 1 1
      src/components/password-input.tsx
  4. 1 1
      src/components/sidebar.tsx
  5. 1 1
      src/components/theme-switch.tsx
  6. 1 1
      src/components/top-nav.tsx
  7. 0 57
      src/components/ui/button.tsx
  8. 1 1
      src/components/ui/calendar.tsx
  9. 1 1
      src/components/user-nav.tsx
  10. 2 6
      src/pages/auth/components/forgot-form.tsx
  11. 7 22
      src/pages/auth/components/sign-up-form.tsx
  12. 7 22
      src/pages/auth/components/user-auth-form.tsx
  13. 1 1
      src/pages/dashboard/index.tsx
  14. 1 1
      src/pages/errors/general-error.tsx
  15. 1 1
      src/pages/errors/maintenance-error.tsx
  16. 1 1
      src/pages/errors/not-found-error.tsx
  17. 1 1
      src/pages/settings/account/account-form.tsx
  18. 1 1
      src/pages/settings/appearance/appearance-form.tsx
  19. 1 1
      src/pages/settings/components/sidebar-nav.tsx
  20. 1 1
      src/pages/settings/display/display-form.tsx
  21. 1 1
      src/pages/settings/notifications/notifications-form.tsx
  22. 1 1
      src/pages/settings/profile/profile-form.tsx
  23. 1 1
      src/pages/tasks/components/data-table-column-header.tsx
  24. 1 1
      src/pages/tasks/components/data-table-faceted-filter.tsx
  25. 1 1
      src/pages/tasks/components/data-table-pagination.tsx
  26. 1 1
      src/pages/tasks/components/data-table-row-actions.tsx
  27. 1 1
      src/pages/tasks/components/data-table-toolbar.tsx
  28. 1 1
      src/pages/tasks/components/data-table-view-options.tsx

+ 88 - 0
src/components/custom/button.tsx

@@ -0,0 +1,88 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+import { IconLoader2 } from '@tabler/icons-react'
+
+const buttonVariants = cva(
+  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
+  {
+    variants: {
+      variant: {
+        default:
+          'bg-primary text-primary-foreground shadow hover:bg-primary/90',
+        destructive:
+          'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
+        outline:
+          'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
+        secondary:
+          'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
+        ghost: 'hover:bg-accent hover:text-accent-foreground',
+        link: 'text-primary underline-offset-4 hover:underline',
+      },
+      size: {
+        default: 'h-9 px-4 py-2',
+        sm: 'h-8 rounded-md px-3 text-xs',
+        lg: 'h-10 rounded-md px-8',
+        icon: 'h-9 w-9',
+      },
+    },
+    defaultVariants: {
+      variant: 'default',
+      size: 'default',
+    },
+  }
+)
+
+export interface ButtonProps
+  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+    VariantProps<typeof buttonVariants> {
+  asChild?: boolean
+  loading?: boolean
+  leftSection?: JSX.Element
+  rightSection?: JSX.Element
+}
+
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+  (
+    {
+      className,
+      variant,
+      size,
+      asChild = false,
+      children,
+      disabled,
+      loading = false,
+      leftSection,
+      rightSection,
+      ...props
+    },
+    ref
+  ) => {
+    const Comp = asChild ? Slot : 'button'
+    return (
+      <Comp
+        className={cn(buttonVariants({ variant, size, className }))}
+        disabled={loading || disabled}
+        ref={ref}
+        {...props}
+      >
+        {((leftSection && loading) ||
+          (!leftSection && !rightSection && loading)) && (
+          <IconLoader2 className='mr-2 h-4 w-4 animate-spin' />
+        )}
+        {!loading && leftSection && <div className='mr-2'>{leftSection}</div>}
+        {children}
+        {!loading && rightSection && <div className='ml-2'>{rightSection}</div>}
+        {rightSection && loading && (
+          <IconLoader2 className='ml-2 h-4 w-4 animate-spin' />
+        )}
+      </Comp>
+    )
+  }
+)
+Button.displayName = 'Button'
+
+// eslint-disable-next-line react-refresh/only-export-components
+export { Button, buttonVariants }

+ 1 - 1
src/components/nav.tsx

@@ -1,6 +1,6 @@
 import { Link } from 'react-router-dom'
 import { IconChevronDown } from '@tabler/icons-react'
-import { Button, buttonVariants } from './ui/button'
+import { Button, buttonVariants } from './custom/button'
 import {
   Collapsible,
   CollapsibleContent,

+ 1 - 1
src/components/password-input.tsx

@@ -1,6 +1,6 @@
 import * as React from 'react'
 import { cn } from '@/lib/utils'
-import { Button } from './ui/button'
+import { Button } from './custom/button'
 import { IconEye, IconEyeOff } from '@tabler/icons-react'
 
 export interface PasswordInputProps

+ 1 - 1
src/components/sidebar.tsx

@@ -1,7 +1,7 @@
 import { useEffect, useState } from 'react'
 import { IconChevronsLeft, IconMenu2, IconX } from '@tabler/icons-react'
 import { Layout, LayoutHeader } from './custom/layout'
-import { Button } from './ui/button'
+import { Button } from './custom/button'
 import Nav from './nav'
 import { cn } from '@/lib/utils'
 import { sidelinks } from '@/data/sidelinks'

+ 1 - 1
src/components/theme-switch.tsx

@@ -1,6 +1,6 @@
 import { IconMoon, IconSun } from '@tabler/icons-react'
 import { useTheme } from './theme-provider'
-import { Button } from './ui/button'
+import { Button } from './custom/button'
 
 export default function ThemeSwitch() {
   const { theme, setTheme } = useTheme()

+ 1 - 1
src/components/top-nav.tsx

@@ -6,7 +6,7 @@ import {
   DropdownMenuItem,
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
-import { Button } from './ui/button'
+import { Button } from './custom/button'
 import { IconMenu } from '@tabler/icons-react'
 
 interface TopNavProps extends React.HTMLAttributes<HTMLElement> {

+ 0 - 57
src/components/ui/button.tsx

@@ -1,57 +0,0 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const buttonVariants = cva(
-  "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
-  {
-    variants: {
-      variant: {
-        default:
-          "bg-primary text-primary-foreground shadow hover:bg-primary/90",
-        destructive:
-          "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
-        outline:
-          "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
-        secondary:
-          "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
-        ghost: "hover:bg-accent hover:text-accent-foreground",
-        link: "text-primary underline-offset-4 hover:underline",
-      },
-      size: {
-        default: "h-9 px-4 py-2",
-        sm: "h-8 rounded-md px-3 text-xs",
-        lg: "h-10 rounded-md px-8",
-        icon: "h-9 w-9",
-      },
-    },
-    defaultVariants: {
-      variant: "default",
-      size: "default",
-    },
-  }
-)
-
-export interface ButtonProps
-  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
-    VariantProps<typeof buttonVariants> {
-  asChild?: boolean
-}
-
-const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
-  ({ className, variant, size, asChild = false, ...props }, ref) => {
-    const Comp = asChild ? Slot : "button"
-    return (
-      <Comp
-        className={cn(buttonVariants({ variant, size, className }))}
-        ref={ref}
-        {...props}
-      />
-    )
-  }
-)
-Button.displayName = "Button"
-
-export { Button, buttonVariants }

+ 1 - 1
src/components/ui/calendar.tsx

@@ -3,7 +3,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@radix-ui/react-icons'
 import { DayPicker } from 'react-day-picker'
 
 import { cn } from '@/lib/utils'
-import { buttonVariants } from '@/components/ui/button'
+import { buttonVariants } from '@/components/custom/button'
 
 export type CalendarProps = React.ComponentProps<typeof DayPicker>
 

+ 1 - 1
src/components/user-nav.tsx

@@ -1,5 +1,5 @@
 import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   DropdownMenu,
   DropdownMenuContent,

+ 2 - 6
src/pages/auth/components/forgot-form.tsx

@@ -3,7 +3,7 @@ import { cn } from '@/lib/utils'
 import { zodResolver } from '@hookform/resolvers/zod'
 import { useForm } from 'react-hook-form'
 import { z } from 'zod'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   Form,
   FormControl,
@@ -13,7 +13,6 @@ import {
   FormMessage,
 } from '@/components/ui/form'
 import { Input } from '@/components/ui/input'
-import { IconLoader2 } from '@tabler/icons-react'
 
 interface ForgotFormProps extends HTMLAttributes<HTMLDivElement> {}
 
@@ -59,10 +58,7 @@ export function ForgotForm({ className, ...props }: ForgotFormProps) {
                 </FormItem>
               )}
             />
-            <Button className='mt-2' disabled={isLoading}>
-              {isLoading && (
-                <IconLoader2 className='mr-2 h-4 w-4 animate-spin' />
-              )}
+            <Button className='mt-2' loading={isLoading}>
               Continue
             </Button>
           </div>

+ 7 - 22
src/pages/auth/components/sign-up-form.tsx

@@ -3,7 +3,7 @@ import { cn } from '@/lib/utils'
 import { zodResolver } from '@hookform/resolvers/zod'
 import { useForm } from 'react-hook-form'
 import { z } from 'zod'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   Form,
   FormControl,
@@ -13,11 +13,7 @@ import {
   FormMessage,
 } from '@/components/ui/form'
 import { Input } from '@/components/ui/input'
-import {
-  IconBrandFacebook,
-  IconBrandGithub,
-  IconLoader2,
-} from '@tabler/icons-react'
+import { IconBrandFacebook, IconBrandGithub } from '@tabler/icons-react'
 import { PasswordInput } from '@/components/password-input'
 
 interface SignUpFormProps extends HTMLAttributes<HTMLDivElement> {}
@@ -108,10 +104,7 @@ export function SignUpForm({ className, ...props }: SignUpFormProps) {
                 </FormItem>
               )}
             />
-            <Button className='mt-2' disabled={isLoading}>
-              {isLoading && (
-                <IconLoader2 className='mr-2 h-4 w-4 animate-spin' />
-              )}
+            <Button className='mt-2' loading={isLoading}>
               Create Account
             </Button>
 
@@ -131,26 +124,18 @@ export function SignUpForm({ className, ...props }: SignUpFormProps) {
                 variant='outline'
                 className='w-full'
                 type='button'
-                disabled={isLoading}
+                loading={isLoading}
+                leftSection={<IconBrandGithub className='h-4 w-4' />}
               >
-                {isLoading ? (
-                  <IconLoader2 className='mr-2 h-4 w-4 animate-spin' />
-                ) : (
-                  <IconBrandGithub className='mr-2 h-4 w-4' />
-                )}{' '}
                 GitHub
               </Button>
               <Button
                 variant='outline'
                 className='w-full'
                 type='button'
-                disabled={isLoading}
+                loading={isLoading}
+                leftSection={<IconBrandFacebook className='h-4 w-4' />}
               >
-                {isLoading ? (
-                  <IconLoader2 className='mr-2 h-4 w-4 animate-spin' />
-                ) : (
-                  <IconBrandFacebook className='mr-2 h-4 w-4' />
-                )}{' '}
                 Facebook
               </Button>
             </div>

+ 7 - 22
src/pages/auth/components/user-auth-form.tsx

@@ -3,11 +3,7 @@ import { useForm } from 'react-hook-form'
 import { Link } from 'react-router-dom'
 import { z } from 'zod'
 import { zodResolver } from '@hookform/resolvers/zod'
-import {
-  IconBrandFacebook,
-  IconBrandGithub,
-  IconLoader2,
-} from '@tabler/icons-react'
+import { IconBrandFacebook, IconBrandGithub } from '@tabler/icons-react'
 import {
   Form,
   FormControl,
@@ -16,7 +12,7 @@ import {
   FormLabel,
   FormMessage,
 } from '@/components/ui/form'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import { Input } from '@/components/ui/input'
 import { PasswordInput } from '@/components/password-input'
 import { cn } from '@/lib/utils'
@@ -97,10 +93,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
                 </FormItem>
               )}
             />
-            <Button className='mt-2' disabled={isLoading}>
-              {isLoading && (
-                <IconLoader2 className='mr-2 h-4 w-4 animate-spin' />
-              )}
+            <Button className='mt-2' loading={isLoading}>
               Login
             </Button>
 
@@ -120,26 +113,18 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
                 variant='outline'
                 className='w-full'
                 type='button'
-                disabled={isLoading}
+                loading={isLoading}
+                leftSection={<IconBrandGithub className='h-4 w-4' />}
               >
-                {isLoading ? (
-                  <IconLoader2 className='mr-2 h-4 w-4 animate-spin' />
-                ) : (
-                  <IconBrandGithub className='mr-2 h-4 w-4' />
-                )}{' '}
                 GitHub
               </Button>
               <Button
                 variant='outline'
                 className='w-full'
                 type='button'
-                disabled={isLoading}
+                loading={isLoading}
+                leftSection={<IconBrandFacebook className='h-4 w-4' />}
               >
-                {isLoading ? (
-                  <IconLoader2 className='mr-2 h-4 w-4 animate-spin' />
-                ) : (
-                  <IconBrandFacebook className='mr-2 h-4 w-4' />
-                )}{' '}
                 Facebook
               </Button>
             </div>

+ 1 - 1
src/pages/dashboard/index.tsx

@@ -1,4 +1,4 @@
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   Card,
   CardContent,

+ 1 - 1
src/pages/errors/general-error.tsx

@@ -1,5 +1,5 @@
 import { useNavigate } from 'react-router-dom'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import { cn } from '@/lib/utils'
 
 interface GeneralErrorProps extends React.HTMLAttributes<HTMLDivElement> {

+ 1 - 1
src/pages/errors/maintenance-error.tsx

@@ -1,4 +1,4 @@
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 
 export default function MaintenanceError() {
   return (

+ 1 - 1
src/pages/errors/not-found-error.tsx

@@ -1,5 +1,5 @@
 import { useNavigate } from 'react-router-dom'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 
 export default function NotFoundError() {
   const navigate = useNavigate()

+ 1 - 1
src/pages/settings/account/account-form.tsx

@@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form'
 import { z } from 'zod'
 import dayjs from 'dayjs'
 import { cn } from '@/lib/utils'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import { Calendar } from '@/components/ui/calendar'
 import {
   Command,

+ 1 - 1
src/pages/settings/appearance/appearance-form.tsx

@@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form'
 import { z } from 'zod'
 
 import { cn } from '@/lib/utils'
-import { Button, buttonVariants } from '@/components/ui/button'
+import { Button, buttonVariants } from '@/components/custom/button'
 import {
   Form,
   FormControl,

+ 1 - 1
src/pages/settings/components/sidebar-nav.tsx

@@ -1,6 +1,6 @@
 import { useState } from 'react'
 import { Link, useLocation, useNavigate } from 'react-router-dom'
-import { buttonVariants } from '@/components/ui/button'
+import { buttonVariants } from '@/components/custom/button'
 import {
   Select,
   SelectContent,

+ 1 - 1
src/pages/settings/display/display-form.tsx

@@ -2,7 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
 import { useForm } from 'react-hook-form'
 import { z } from 'zod'
 
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import { Checkbox } from '@/components/ui/checkbox'
 import {
   Form,

+ 1 - 1
src/pages/settings/notifications/notifications-form.tsx

@@ -2,7 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
 import { useForm } from 'react-hook-form'
 import { z } from 'zod'
 
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import { Checkbox } from '@/components/ui/checkbox'
 import {
   Form,

+ 1 - 1
src/pages/settings/profile/profile-form.tsx

@@ -1,7 +1,7 @@
 import { z } from 'zod'
 import { Link } from 'react-router-dom'
 import { useFieldArray, useForm } from 'react-hook-form'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   Form,
   FormControl,

+ 1 - 1
src/pages/tasks/components/data-table-column-header.tsx

@@ -6,7 +6,7 @@ import {
 } from '@radix-ui/react-icons'
 import { Column } from '@tanstack/react-table'
 
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   DropdownMenu,
   DropdownMenuContent,

+ 1 - 1
src/pages/tasks/components/data-table-faceted-filter.tsx

@@ -4,7 +4,7 @@ import { Column } from '@tanstack/react-table'
 
 import { cn } from '@/lib/utils'
 import { Badge } from '@/components/ui/badge'
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   Command,
   CommandEmpty,

+ 1 - 1
src/pages/tasks/components/data-table-pagination.tsx

@@ -6,7 +6,7 @@ import {
 } from '@radix-ui/react-icons'
 import { Table } from '@tanstack/react-table'
 
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   Select,
   SelectContent,

+ 1 - 1
src/pages/tasks/components/data-table-row-actions.tsx

@@ -1,7 +1,7 @@
 import { DotsHorizontalIcon } from '@radix-ui/react-icons'
 import { Row } from '@tanstack/react-table'
 
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   DropdownMenu,
   DropdownMenuContent,

+ 1 - 1
src/pages/tasks/components/data-table-toolbar.tsx

@@ -1,7 +1,7 @@
 import { Cross2Icon } from '@radix-ui/react-icons'
 import { Table } from '@tanstack/react-table'
 
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import { Input } from '@/components/ui/input'
 import { DataTableViewOptions } from '../components/data-table-view-options'
 

+ 1 - 1
src/pages/tasks/components/data-table-view-options.tsx

@@ -2,7 +2,7 @@ import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu'
 import { MixerHorizontalIcon } from '@radix-ui/react-icons'
 import { Table } from '@tanstack/react-table'
 
-import { Button } from '@/components/ui/button'
+import { Button } from '@/components/custom/button'
 import {
   DropdownMenu,
   DropdownMenuCheckboxItem,