Particular.Tech
Formsui

Secret input

Masked API key or secret field with reveal and copy.

Pooled connection string for the main branch.

Installation

npx shadcn@latest add @particular/secret-input
npm dependencies
  • cn@^0.4.0
  • lucide-react@^1.51.0
Installs these registry items

Source

ui/secret-input.tsx
"use client"

import * as React from "react"
import { EyeIcon, EyeOffIcon } from "lucide-react"
import { cn } from "cn"

import { CopyButton } from "@/components/ui/copy-button"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group"

type SecretInputProps = Omit<React.ComponentProps<"input">, "type" | "value" | "defaultValue" | "onCopy"> & {
  value: string
  revealed?: boolean
  defaultRevealed?: boolean
  onRevealedChange?: (revealed: boolean) => void
  copyable?: boolean
  onCopied?: (value: string) => void
}

function SecretInput({
  value,
  revealed: revealedProp,
  defaultRevealed = false,
  onRevealedChange,
  copyable = true,
  onCopied,
  readOnly,
  className,
  ...props
}: SecretInputProps) {
  const [uncontrolled, setUncontrolled] = React.useState(defaultRevealed)
  const revealed = revealedProp ?? uncontrolled

  const toggle = () => {
    const next = !revealed
    if (revealedProp === undefined) setUncontrolled(next)
    onRevealedChange?.(next)
  }

  return (
    <InputGroup data-slot="secret-input" className={className}>
      <InputGroupInput
        type={revealed ? "text" : "password"}
        value={value}
        readOnly={readOnly ?? props.onChange === undefined}
        autoComplete="off"
        spellCheck={false}
        className={cn("font-mono text-[0.8125rem]", !revealed && "tracking-wider")}
        {...props}
      />
      <InputGroupAddon align="inline-end">
        <InputGroupButton
          size="icon-xs"
          aria-label={revealed ? "Hide value" : "Show value"}
          aria-pressed={revealed}
          onClick={toggle}
        >
          {revealed ? <EyeOffIcon /> : <EyeIcon />}
        </InputGroupButton>
        {copyable && <CopyButton value={value} size="icon-xs" label="Copy value" onCopied={onCopied} />}
      </InputGroupAddon>
    </InputGroup>
  )
}

export { SecretInput, type SecretInputProps }