Tailwind CSS React Input 组件

109 min read
import * as React from 'react'
import TextareaAutosize from 'react-autosize-textarea'

const styles =
  'w-full rounded-md text-primary px-4 py-2 text-primary bg-gray-1000 dark:bg-white dark:bg-opacity-5 bg-opacity-5 hover border-gray-200 dark:border-gray-700'

export function Input(props) {
  return <input className={styles} {...props} />
}

export function Textarea({ maxRows = 8, rows = 1, ...props }) {
  return (
    <TextareaAutosize
      maxRows={maxRows}
      rows={rows}
      className={`${styles} block`}
      {...props}
    />
  )
}

export function Select(props) {
  return <select className={styles} {...props} />
}

使用

import * as React from 'react'

import { WarnAlert } from '~/components/Alert'
import Button from '~/components/Button'
import { Input } from '~/components/Input'
import { LoadingSpinner } from '~/components/LoadingSpinner'
import { GET_VIEWER_SETTINGS } from '~/graphql/queries/viewer'
import {
  GetViewerWithSettingsQuery,
  useEditUserMutation,
} from '~/graphql/types.generated'

export function EmailForm(props: {
  viewer: GetViewerWithSettingsQuery['viewer']
}) {
  const { viewer } = props
  const isNew = !viewer.email && !viewer.pendingEmail

  const [isEditing, setIsEditing] = React.useState(isNew)
  const [email, setEmail] = React.useState('')

  const [setPendingEmail, setPendingEmailResponse] = useEditUserMutation({
    variables: {
      data: {
        email,
      },
    },
    update(cache) {
      const { viewer } = cache.readQuery({
        query: GET_VIEWER_SETTINGS,
      })

      cache.writeQuery({
        query: GET_VIEWER_SETTINGS,
        data: {
          viewer: {
            ...viewer,
            pendingEmail: email === viewer.email ? null : email,
          },
        },
      })
    },
    onError() {},
    onCompleted() {
      setIsEditing(false)
    },
  })

  const [cancelPendingEmail, cancelPendingEmailResponse] = useEditUserMutation({
    variables: {
      data: {
        email: null,
      },
    },
    update(cache) {
      const { viewer } = cache.readQuery({
        query: GET_VIEWER_SETTINGS,
      })

      cache.writeQuery({
        query: GET_VIEWER_SETTINGS,
        data: {
          viewer: {
            ...viewer,
            pendingEmail: null,
          },
        },
      })
    },
    onError() {},
    onCompleted() {
      setEmail('')
    },
  })

  const [resendPendingEmail, resendPendingEmailResponse] = useEditUserMutation({
    variables: {
      data: {
        email: viewer.pendingEmail,
      },
    },
    update(cache) {
      const { viewer } = cache.readQuery({
        query: GET_VIEWER_SETTINGS,
      })

      cache.writeQuery({
        query: GET_VIEWER_SETTINGS,
        data: {
          viewer,
        },
      })
    },
    onError() {},
  })

  function onSubmit(e) {
    e.preventDefault()
    if (setPendingEmailResponse.loading) return

    if (email === viewer.email) {
      return setIsEditing(false)
    }

    setPendingEmail()
  }

  function handleEmailChange(e) {
    setEmail(e.target.value.trim())
  }

  return (
    <div className="space-y-2">
      <p className="text-primary font-semibold">Email</p>

      {viewer.email && (
        <div className="text-primary flex space-x-2">
          <span>{viewer.email}</span>
          <span>·</span>
          <button
            className="cursor-pointer font-medium text-blue-500"
            onClick={() => setIsEditing(!isEditing)}
          >
            {isEditing ? 'Cancel' : 'Edit'}
          </button>
        </div>
      )}

      {(isNew || isEditing) && (
        <form className="space-y-2" onSubmit={onSubmit}>
          {isNew && (
            <p className="text-quaternary text-sm">
              Adding your email will allow you to turn on replies for comments
              or AMA questions.
            </p>
          )}
          <Input
            type="email"
            placeholder={
              isNew ? 'Add your email address' : 'Update your email address'
            }
            value={email}
            autoFocus
            onChange={handleEmailChange}
          />
          <div className="flex justify-between">
            <Button type="submit">
              {setPendingEmailResponse.loading ? (
                <LoadingSpinner />
              ) : (
                'Save email'
              )}
            </Button>
          </div>
        </form>
      )}

      {viewer.pendingEmail && !isEditing && (
        <>
          <WarnAlert>
            <div className="flex flex-col space-y-2">
              <div>
                Check <span className="font-medium">{viewer.pendingEmail}</span>{' '}
                to confirm your email address
              </div>
              <div className="flex space-x-2">
                <Button onClick={cancelPendingEmail}>
                  {cancelPendingEmailResponse.loading ? (
                    <LoadingSpinner />
                  ) : (
                    'Cancel request'
                  )}
                </Button>
                <Button onClick={resendPendingEmail} type="submit">
                  {resendPendingEmailResponse.loading ? (
                    <LoadingSpinner />
                  ) : (
                    'Resend confirmation'
                  )}
                </Button>
              </div>
            </div>
          </WarnAlert>
        </>
      )}
    </div>
  )
}