Parcourir la source

fix: update JSX types due to react 19

satnaing il y a 1 an
Parent
commit
44cea342a9

+ 2 - 2
src/components/button.tsx

@@ -44,8 +44,8 @@ type ButtonProps = ButtonPropsBase &
     | {
         asChild?: false
         loading?: boolean
-        leftSection?: JSX.Element
-        rightSection?: JSX.Element
+        leftSection?: React.JSX.Element
+        rightSection?: React.JSX.Element
       }
   )
 

+ 1 - 1
src/components/confirm-dialog.tsx

@@ -15,7 +15,7 @@ interface ConfirmDialogProps {
   onOpenChange: (open: boolean) => void
   title: React.ReactNode
   disabled?: boolean
-  desc: JSX.Element | string
+  desc: React.JSX.Element | string
   cancelBtnText?: string
   confirmText?: React.ReactNode
   destructive?: boolean

+ 126 - 142
src/components/pin-input.tsx

@@ -70,124 +70,123 @@ interface PinInputProps {
    * If set, the input fields are disabled, `false` by default
    */
   disabled?: boolean
+  ref?: React.Ref<HTMLDivElement>
 }
 
 const PinInputContext = React.createContext<boolean>(false)
 
-const PinInput = React.forwardRef<HTMLDivElement, PinInputProps>(
-  ({ className, children, ...props }, ref) => {
-    const {
-      defaultValue,
-      value,
-      onChange,
-      onComplete,
-      onIncomplete,
-      placeholder = '○',
-      type = 'alphanumeric',
-      name,
-      form,
-      otp = false,
-      mask = false,
-      disabled = false,
-      readOnly = false,
-      autoFocus = false,
-      ariaLabel = '',
-      ...rest
-    } = props
-
-    const validChildren = getValidChildren(children)
-
-    const length = getInputFieldCount(children)
-
-    // pins, pinValue, refMap, ...handlers
-    const { pins, pinValue, refMap, ...handlers } = usePinInput({
-      value,
-      defaultValue,
-      placeholder,
-      type,
-      length,
-      readOnly,
-    })
-
-    /* call onChange func if pinValue changes */
-    React.useEffect(() => {
-      if (!onChange) return
-      onChange(pinValue)
-    }, [onChange, pinValue])
-
-    /* call onComplete/onIncomplete func if pinValue is valid and completed/incompleted */
-    const completeRef = React.useRef(pinValue.length === length)
-    React.useEffect(() => {
-      if (pinValue.length === length && completeRef.current === false) {
-        completeRef.current = true
-        if (onComplete) onComplete(pinValue)
-      }
-      if (pinValue.length !== length && completeRef.current === true) {
-        completeRef.current = false
-        if (onIncomplete) onIncomplete(pinValue)
-      }
-    }, [length, onComplete, onIncomplete, pinValue, pins, value])
-
-    /* focus on first input field if autoFocus is set */
-    React.useEffect(() => {
-      if (!autoFocus) return
-      const node = refMap?.get(0)
-      if (node) {
-        node.focus()
-      }
-    }, [autoFocus, refMap])
-
-    const skipRef = React.useRef(0)
-    let counter = 0
-    const clones = validChildren.map((child) => {
-      if (child.type === PinInputField) {
-        const pinIndex = counter
-        counter = counter + 1
-        return React.cloneElement(child, {
-          name,
-          inputKey: `input-${pinIndex}`,
-          value: length > pinIndex ? pins[pinIndex] : '',
-          onChange: (e: React.ChangeEvent<HTMLInputElement>) =>
-            handlers.handleChange(e, pinIndex),
-          onFocus: (e: React.FocusEvent<HTMLInputElement>) =>
-            handlers.handleFocus(e, pinIndex),
-          onBlur: () => handlers.handleBlur(pinIndex),
-          onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) =>
-            handlers.handleKeyDown(e, pinIndex),
-          onPaste: (e: React.ClipboardEvent<HTMLInputElement>) =>
-            handlers.handlePaste(e),
-          placeholder: placeholder,
-          type: type,
-          mask: mask,
-          autoComplete: otp ? 'one-time-code' : 'off',
-          disabled: disabled,
-          readOnly: readOnly,
-          'aria-label': ariaLabel
-            ? ariaLabel
-            : `Pin input ${counter} of ${length}`,
-          ref: (node: HTMLInputElement | null) => {
-            if (node) {
-              refMap?.set(pinIndex, node)
-            } else {
-              refMap?.delete(pinIndex)
-            }
-          },
-        })
-      }
-      skipRef.current = skipRef.current + 1
-      return child
-    })
-
-    return (
-      <PinInputContext.Provider value={true}>
-        <div ref={ref} aria-label='Pin Input' className={className} {...rest}>
-          {clones}
-          <input type='hidden' name={name} form={form} value={pinValue} />
-        </div>
-      </PinInputContext.Provider>
-    )
-  }
-)
+const PinInput = ({ className, children, ref, ...props }: PinInputProps) => {
+  const {
+    defaultValue,
+    value,
+    onChange,
+    onComplete,
+    onIncomplete,
+    placeholder = '○',
+    type = 'alphanumeric',
+    name,
+    form,
+    otp = false,
+    mask = false,
+    disabled = false,
+    readOnly = false,
+    autoFocus = false,
+    ariaLabel = '',
+    ...rest
+  } = props
+
+  const validChildren = getValidChildren(children)
+
+  const length = getInputFieldCount(children)
+
+  // pins, pinValue, refMap, ...handlers
+  const { pins, pinValue, refMap, ...handlers } = usePinInput({
+    value,
+    defaultValue,
+    placeholder,
+    type,
+    length,
+    readOnly,
+  })
+
+  /* call onChange func if pinValue changes */
+  React.useEffect(() => {
+    if (!onChange) return
+    onChange(pinValue)
+  }, [onChange, pinValue])
+
+  /* call onComplete/onIncomplete func if pinValue is valid and completed/incompleted */
+  const completeRef = React.useRef(pinValue.length === length)
+  React.useEffect(() => {
+    if (pinValue.length === length && completeRef.current === false) {
+      completeRef.current = true
+      if (onComplete) onComplete(pinValue)
+    }
+    if (pinValue.length !== length && completeRef.current === true) {
+      completeRef.current = false
+      if (onIncomplete) onIncomplete(pinValue)
+    }
+  }, [length, onComplete, onIncomplete, pinValue, pins, value])
+
+  /* focus on first input field if autoFocus is set */
+  React.useEffect(() => {
+    if (!autoFocus) return
+    const node = refMap?.get(0)
+    if (node) {
+      node.focus()
+    }
+  }, [autoFocus, refMap])
+
+  const skipRef = React.useRef(0)
+  let counter = 0
+  const clones = validChildren.map((child) => {
+    if (child.type === PinInputField) {
+      const pinIndex = counter
+      counter = counter + 1
+      return React.cloneElement(child, {
+        name,
+        inputKey: `input-${pinIndex}`,
+        value: length > pinIndex ? pins[pinIndex] : '',
+        onChange: (e: React.ChangeEvent<HTMLInputElement>) =>
+          handlers.handleChange(e, pinIndex),
+        onFocus: (e: React.FocusEvent<HTMLInputElement>) =>
+          handlers.handleFocus(e, pinIndex),
+        onBlur: () => handlers.handleBlur(pinIndex),
+        onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) =>
+          handlers.handleKeyDown(e, pinIndex),
+        onPaste: (e: React.ClipboardEvent<HTMLInputElement>) =>
+          handlers.handlePaste(e),
+        placeholder: placeholder,
+        type: type,
+        mask: mask,
+        autoComplete: otp ? 'one-time-code' : 'off',
+        disabled: disabled,
+        readOnly: readOnly,
+        'aria-label': ariaLabel
+          ? ariaLabel
+          : `Pin input ${counter} of ${length}`,
+        ref: (node: HTMLInputElement | null) => {
+          if (node) {
+            refMap?.set(pinIndex, node)
+          } else {
+            refMap?.delete(pinIndex)
+          }
+        },
+      })
+    }
+    skipRef.current = skipRef.current + 1
+    return child
+  })
+
+  return (
+    <PinInputContext.Provider value={true}>
+      <div ref={ref} aria-label='Pin Input' className={className} {...rest}>
+        {clones}
+        <input type='hidden' name={name} form={form} value={pinValue} />
+      </div>
+    </PinInputContext.Provider>
+  )
+}
 PinInput.displayName = 'PinInput'
 
 /* ========== PinInputField ========== */
@@ -206,17 +205,14 @@ interface PinInputFieldProps<T>
   component?: T
 }
 
-const PinInputFieldNoRef = <T extends React.ElementType = 'input'>(
-  {
-    className,
-    component,
-    ...props
-  }: PinInputFieldProps<T> &
-    (React.ComponentType<T> extends undefined
-      ? never
-      : React.ComponentProps<T>),
-  ref: React.ForwardedRef<HTMLInputElement>
-) => {
+const PinInputField = <T extends React.ElementType = 'input'>({
+  className,
+  component,
+  ...props
+}: PinInputFieldProps<T> &
+  (React.ComponentType<T> extends undefined
+    ? never
+    : React.ComponentProps<T>)) => {
   const { mask, type, inputKey, ...rest } = props as _PinInputFieldProps &
     React.ComponentProps<T>
 
@@ -233,7 +229,6 @@ const PinInputFieldNoRef = <T extends React.ElementType = 'input'>(
   return (
     <Element
       key={inputKey}
-      ref={ref}
       type={mask ? 'password' : type === 'numeric' ? 'tel' : 'text'}
       inputMode={type === 'numeric' ? 'numeric' : 'text'}
       className={cn('size-10 text-center', className)}
@@ -242,17 +237,6 @@ const PinInputFieldNoRef = <T extends React.ElementType = 'input'>(
   )
 }
 
-const PinInputField = React.forwardRef(PinInputFieldNoRef) as <
-  T extends React.ElementType = 'input',
->(
-  {
-    className,
-    component,
-    ...props
-  }: PinInputFieldProps<T> & React.ComponentProps<T>,
-  ref: React.ForwardedRef<HTMLInputElement>
-) => JSX.Element
-
 /* ========== usePinInput custom hook ========== */
 
 interface UsePinInputProps {
@@ -284,13 +268,12 @@ const usePinInput = ({
     [defaultValue, length, value]
   )
 
-  const [pins, setPins] = React.useState<(string | number)[]>(pinInputs)
+  const [pins, setPins] = React.useState(pinInputs)
   const pinValue = pins.join('').trim()
 
   /**
-   * Update pins when values changes.
-   * This is necessary and this allows pins to be synced
-   * when the value is update or reset programmatically
+   * Update pins when values change programmatically.
+   * This syncs the pins if the `defaultValue` or `value` prop is updated.
    */
   React.useEffect(() => {
     setPins(pinInputs)
@@ -458,7 +441,8 @@ const getValidChildren = (children: React.ReactNode) =>
       return React.isValidElement(child)
     }
     throw new Error(`${PinInput.displayName} contains invalid children.`)
-  }) as React.ReactElement[]
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  }) as React.ReactElement<any>[]
 
 const getInputFieldCount = (children: React.ReactNode) =>
   React.Children.toArray(children).filter((child) => {

+ 13 - 4
src/features/auth/otp/components/otp-form.tsx

@@ -2,7 +2,9 @@ import { HTMLAttributes, useState } from 'react'
 import { z } from 'zod'
 import { useForm } from 'react-hook-form'
 import { zodResolver } from '@hookform/resolvers/zod'
+import { useNavigate } from '@tanstack/react-router'
 import { cn } from '@/lib/utils'
+import { toast } from '@/hooks/use-toast'
 import {
   Form,
   FormControl,
@@ -22,6 +24,7 @@ const formSchema = z.object({
 })
 
 export function OtpForm({ className, ...props }: OtpFormProps) {
+  const navigate = useNavigate()
   const [isLoading, setIsLoading] = useState(false)
   const [disabledBtn, setDisabledBtn] = useState(true)
 
@@ -32,13 +35,19 @@ export function OtpForm({ className, ...props }: OtpFormProps) {
 
   function onSubmit(data: z.infer<typeof formSchema>) {
     setIsLoading(true)
-    // eslint-disable-next-line no-console
-    console.log({ data })
+    toast({
+      title: 'You submitted the following values:',
+      description: (
+        <pre className='mt-2 w-[340px] rounded-md bg-slate-950 p-4'>
+          <code className='text-white'>{JSON.stringify(data, null, 2)}</code>
+        </pre>
+      ),
+    })
 
     setTimeout(() => {
-      form.reset()
       setIsLoading(false)
-    }, 2000)
+      navigate({ to: '/' })
+    }, 1000)
   }
 
   return (

+ 1 - 1
src/features/settings/components/content-section.tsx

@@ -4,7 +4,7 @@ import { Separator } from '@/components/ui/separator'
 interface ContentSectionProps {
   title: string
   desc: string
-  children: JSX.Element
+  children: React.JSX.Element
 }
 
 export default function ContentSection({

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

@@ -1,4 +1,4 @@
-import { useState } from 'react'
+import { useState, type JSX } from 'react'
 import { useLocation, useNavigate } from '@tanstack/react-router'
 import { Link } from '@tanstack/react-router'
 import { cn } from '@/lib/utils'