{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input-35",
  "title": "Input with characters left",
  "description": "A hint under the field that counts what remains.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/field.json",
    "https://ui.ndekocode.dev/r/input.json",
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/use-character-limit.json",
    "https://ui.ndekocode.dev/r/use-prefers-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/components/input/input-35.tsx",
      "content": "'use client';\n\nimport {\n  AnimatePresence,\n  motion,\n  type Transition,\n  type Variants,\n} from 'motion/react';\nimport { useState, type ChangeEvent } from 'react';\n\nimport { useCharacterLimit } from '@/registry/ndeko/hooks/use-character-limit';\nimport { usePrefersReducedMotion } from '@/registry/ndeko/hooks/use-prefers-reduced-motion';\nimport { Field, FieldDescription, FieldLabel } from '@/registry/ndeko/ui/field';\nimport { Input } from '@/registry/ndeko/ui/input';\n\nconst MAX_LENGTH = 8;\nconst DIGIT_SHIFT_PX = 8;\n\nconst DIGIT_TRANSITION: Transition = { duration: 0.18, ease: [0.2, 0, 0, 1] };\n\nconst DIGIT_VARIANTS: Variants = {\n  enter: (direction: number) => {\n    const offset = direction * DIGIT_SHIFT_PX;\n    return { opacity: 0, y: offset };\n  },\n  visible: { opacity: 1, y: 0 },\n  exit: (direction: number) => {\n    const offset = direction * DIGIT_SHIFT_PX;\n    return { opacity: 0, y: -offset };\n  },\n};\n\nconst FADE_VARIANTS: Variants = {\n  enter: { opacity: 0 },\n  visible: { opacity: 1 },\n  exit: { opacity: 0 },\n};\n\nfunction digitVariantsOf(shouldReduceMotion: boolean | null) {\n  if (shouldReduceMotion) {\n    return FADE_VARIANTS;\n  }\n  return DIGIT_VARIANTS;\n}\n\nfunction directionOf(previous: number, next: number) {\n  if (next < previous) {\n    return -1;\n  }\n  return 1;\n}\n\nfunction characterWordOf(count: number) {\n  if (count === 1) {\n    return 'character';\n  }\n  return 'characters';\n}\n\nexport default function Input35() {\n  const [direction, setDirection] = useState(-1);\n  const { value, characterCount, handleChange, maxLength } = useCharacterLimit({\n    maxLength: MAX_LENGTH,\n  });\n  const shouldReduceMotion = usePrefersReducedMotion();\n  const digitVariants = digitVariantsOf(shouldReduceMotion);\n  const charactersLeft = maxLength - characterCount;\n  const characterWord = characterWordOf(charactersLeft);\n\n  function changeCode(event: ChangeEvent<HTMLInputElement>) {\n    const nextLeft = maxLength - event.currentTarget.value.length;\n    setDirection(directionOf(charactersLeft, nextLeft));\n    handleChange(event);\n  }\n\n  return (\n    <Field className='w-full max-w-xs'>\n      <FieldLabel>Promo code</FieldLabel>\n      <Input\n        placeholder='SPRING24'\n        value={value}\n        maxLength={maxLength}\n        onChange={changeCode}\n      />\n      <FieldDescription>\n        <span className='flex items-center gap-1' aria-hidden='true'>\n          <span className='relative flex tabular-nums'>\n            <AnimatePresence\n              mode='popLayout'\n              initial={false}\n              custom={direction}\n            >\n              <motion.span\n                key={charactersLeft}\n                className='flex'\n                custom={direction}\n                variants={digitVariants}\n                initial='enter'\n                animate='visible'\n                exit='exit'\n                transition={DIGIT_TRANSITION}\n              >\n                {charactersLeft}\n              </motion.span>\n            </AnimatePresence>\n          </span>\n          {characterWord} left\n        </span>\n        <span className='sr-only' role='status'>\n          {charactersLeft} {characterWord} left\n        </span>\n      </FieldDescription>\n    </Field>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "components",
    "input"
  ],
  "type": "registry:component"
}