{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "journal-navigation",
  "type": "registry:component",
  "title": "Journal Navigation",
  "description": "Organized journal navigation enabling quick section switching with smooth animated transitions.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "components/watermelon/journal-navigation.tsx",
      "type": "registry:component",
      "content": "import React, { useState, useEffect, useRef } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\n\nexport interface JournalEntry {\n  id: string | number;\n  day: number;\n  month: string;\n  year?: number;\n  content: React.ReactNode;\n}\n\ninterface JournalNavigationProps {\n  entries: JournalEntry[];\n  initialIndex?: number;\n  onEntryChange?: (entry: JournalEntry) => void;\n}\n\nexport const JournalNavigation: React.FC<JournalNavigationProps> = ({\n  entries,\n  initialIndex = 0,\n  onEntryChange,\n}) => {\n  const [currentIndex, setCurrentIndex] = useState(initialIndex);\n  const [direction, setDirection] = useState(0);\n\n  const directionRef = useRef(direction);\n  useEffect(() => {\n    directionRef.current = direction;\n  }, [direction]);\n\n  const handleNext = () => {\n    if (currentIndex < entries.length - 1) {\n      setDirection(1);\n      setCurrentIndex((prev) => prev + 1);\n    }\n  };\n\n  const handlePrev = () => {\n    if (currentIndex > 0) {\n      setDirection(-1);\n      setCurrentIndex((prev) => prev - 1);\n    }\n  };\n\n  useEffect(() => {\n    if (onEntryChange) {\n      onEntryChange(entries[currentIndex]);\n    }\n  }, [currentIndex, entries, onEntryChange]);\n\n  const currentEntry = entries[currentIndex];\n\n  const contentVariants = {\n    enter: (direction: number) => ({\n      y: direction > 0 ? 3 : -3,\n      opacity: 0,\n    }),\n    center: { y: 0, opacity: 1 },\n    exit: (direction: number) => ({\n      y: direction > 0 ? 3 : -3,\n      opacity: 0,\n    }),\n  };\n\n  const charVariants = {\n    enter: () => ({\n      // Wait to spawn until old leaves? The user wants exactly:\n      // \"old div content vanshies and as soon as one char vanish new content appear\"\n      // They just want a beautiful overlapping displacement\n      y: directionRef.current > 0 ? 3 : -3,\n      opacity: 0,\n      filter: 'blur(2px)',\n    }),\n    center: (globalIndex: number) => ({\n      y: 0,\n      opacity: 1,\n      filter: 'blur(0px)',\n      transition: {\n        type: 'spring' as const,\n        bounce: 0.1,\n        duration: 0.3,\n        delay: globalIndex * 0.01,\n      },\n    }),\n    exit: (globalIndex: number) => ({\n      y: directionRef.current > 0 ? 3 : -3,\n      opacity: 0,\n      filter: 'blur(2px)',\n      transition: {\n        type: 'spring' as const,\n        bounce: 0.1,\n        duration: 0.3,\n        delay: globalIndex * 0.01, // perfectly match the enter speed to spin at same time\n      },\n    }),\n  };\n\n  const ITEM_HEIGHT = 45; // 37px button height (h-9.25) + 8px gap (gap-2)\n\n  return (\n    <div className=\"flex min-h-full w-full flex-col items-center justify-center bg-transparent p-6 transition-colors duration-500\">\n      <div className=\"relative flex h-85 w-full max-w-90 overflow-hidden rounded-[32px] border border-[#e5e4de]/50 bg-[#F3EFE9] shadow-sm transition-colors duration-300 select-none dark:border-neutral-800 dark:bg-neutral-900\">\n        <div className=\"relative z-10 m-1 flex w-13.5 flex-col items-center justify-center overflow-hidden rounded-full border border-[#e5e4de]/50 bg-[#FEFEFE] transition-colors duration-300 dark:border-neutral-700 dark:bg-neutral-800\">\n          <div className=\"pointer-events-none absolute top-0 left-0 z-20 h-20 w-full bg-linear-to-b from-[#FEFEFE] via-[#FEFEFE]/80 to-transparent backdrop-blur-[0.5px] dark:from-neutral-800 dark:via-neutral-800/80 dark:to-transparent\" />\n\n          <div\n            className=\"absolute top-1/2 left-0 z-10 w-full\"\n            style={{ marginTop: '-18.5px' }}\n          >\n            <motion.div\n              drag=\"y\"\n              dragConstraints={{\n                top: -((entries.length - 1) * ITEM_HEIGHT),\n                bottom: 0,\n              }}\n              onDragEnd={(_, info) => {\n                const yOffset = info.offset.y;\n                const velocity = info.velocity.y;\n                const absOffset = Math.abs(yOffset);\n\n                // Need to move at least 15px or flick fast to change index\n                let itemsToMove = Math.floor((absOffset + 15) / ITEM_HEIGHT);\n\n                if (Math.abs(velocity) > 200 && itemsToMove === 0) {\n                  itemsToMove = 1;\n                }\n\n                const directionMultiplier = yOffset < 0 ? 1 : -1;\n                let newIndex = currentIndex + directionMultiplier * itemsToMove;\n\n                if (newIndex < 0) newIndex = 0;\n                if (newIndex >= entries.length) newIndex = entries.length - 1;\n\n                if (newIndex > currentIndex) setDirection(1);\n                else if (newIndex < currentIndex) setDirection(-1);\n\n                if (newIndex !== currentIndex) {\n                  setCurrentIndex(newIndex);\n                }\n              }}\n              animate={{ y: -(currentIndex * ITEM_HEIGHT) }}\n              transition={{\n                type: 'spring',\n                stiffness: 260,\n                damping: 32,\n                mass: 0.6,\n              }}\n              className=\"flex cursor-grab flex-col items-center gap-2 active:cursor-grabbing\"\n            >\n              {entries.map((entry, index) => {\n                const isActive = index === currentIndex;\n                return (\n                  <motion.button\n                    key={entry.id}\n                    onClick={() => {\n                      if (index > currentIndex) setDirection(1);\n                      else if (index < currentIndex) setDirection(-1);\n                      if (index !== currentIndex) {\n                        setCurrentIndex(index);\n                      }\n                    }}\n                    animate={{ scale: isActive ? 1.2 : 1 }}\n                    className={`flex h-9.25 w-9.25 shrink-0 items-center justify-center rounded-full text-[16px] font-bold transition-colors ${\n                      isActive\n                        ? 'bg-[#F0ECE6] text-[#1C1C1E] dark:bg-neutral-700 dark:text-white'\n                        : 'text-[#B0AFB8] hover:bg-[#F0ECE6] dark:text-neutral-600 dark:hover:text-neutral-400'\n                    }`}\n                  >\n                    {entry.day.toString().padStart(2, '0')}\n                  </motion.button>\n                );\n              })}\n            </motion.div>\n          </div>\n\n          <div className=\"pointer-events-none absolute bottom-0 left-0 z-20 h-20 w-full bg-linear-to-t from-[#FEFEFE] via-[#FEFEFE]/80 to-transparent backdrop-blur-[0.5px] dark:from-neutral-800 dark:via-neutral-800/80 dark:to-transparent\" />\n        </div>\n\n        <div className=\"flex flex-1 flex-col p-4\">\n          <div className=\"mb-6 flex items-center justify-between\">\n            <div className=\"relative flex items-center overflow-hidden text-lg font-medium tracking-tight text-[#918D87] tabular-nums dark:text-neutral-400\">\n              <AnimatePresence mode=\"popLayout\" custom={direction}>\n                <motion.span\n                  key={currentEntry.month}\n                  custom={direction}\n                  variants={contentVariants}\n                  initial=\"enter\"\n                  animate=\"center\"\n                  exit=\"exit\"\n                  transition={{ duration: 0.3, ease: 'easeOut' }}\n                  className=\"mr-1 inline-block whitespace-nowrap\"\n                >\n                  {currentEntry.month}\n                </motion.span>\n              </AnimatePresence>\n              <div className=\"flex\">\n                {currentEntry.day\n                  .toString()\n                  .split('')\n                  .map((digit, i) => (\n                    <AnimatePresence\n                      mode=\"popLayout\"\n                      custom={direction}\n                      key={i}\n                    >\n                      <motion.span\n                        key={`${i}-${digit}`}\n                        custom={direction}\n                        variants={contentVariants}\n                        initial=\"enter\"\n                        animate=\"center\"\n                        exit=\"exit\"\n                        transition={{ duration: 0.3, ease: 'easeOut' }}\n                        className=\"inline-block\"\n                      >\n                        {digit}\n                      </motion.span>\n                    </AnimatePresence>\n                  ))}\n              </div>\n            </div>\n\n            <div className=\"flex gap-2\">\n              {[\n                {\n                  title: 'left',\n                  action: handlePrev,\n                  disabled: currentIndex === 0,\n                  icon: <ChevronLeft size={20} strokeWidth={2.5} />,\n                },\n                {\n                  title: 'right',\n                  action: handleNext,\n                  disabled: currentIndex === entries.length - 1,\n                  icon: <ChevronRight size={20} strokeWidth={2.5} />,\n                },\n              ].map((btn) => (\n                <button\n                  key={btn.title}\n                  title={btn.title}\n                  onClick={btn.action}\n                  disabled={btn.disabled}\n                  className=\"flex h-8 w-8 items-center justify-center rounded-full bg-[#Fefefe] text-[#B8B8B5] transition-colors hover:bg-[#Fefefe]/70 disabled:hover:bg-[#f2f1eb] dark:bg-neutral-800 dark:text-neutral-500 dark:hover:bg-neutral-700 dark:disabled:opacity-20\"\n                >\n                  {btn.icon}\n                </button>\n              ))}\n            </div>\n          </div>\n\n          <div className=\"flex flex-1 flex-col overflow-hidden\">\n            <div className=\"relative flex-1\">\n              <AnimatePresence>\n                <motion.div\n                  key={currentEntry.id + '-stagger'}\n                  variants={{\n                    enter: { opacity: 1 },\n                    center: { opacity: 1 },\n                    exit: { opacity: 1, transition: { duration: 1.5 } }, // hold parent alive\n                  }}\n                  initial=\"enter\"\n                  animate=\"center\"\n                  exit=\"exit\"\n                  className=\"absolute top-0 left-0 w-full text-[18px] leading-relaxed font-bold -tracking-wide text-[#292422] transition-colors dark:text-neutral-200\"\n                >\n                  {typeof currentEntry.content === 'string' ||\n                  Array.isArray(currentEntry.content) ? (\n                    <motion.div className=\"space-y-4\">\n                      {(Array.isArray(currentEntry.content)\n                        ? currentEntry.content\n                        : [currentEntry.content as string]\n                      ).map((paragraph, pIndex, arr) => {\n                        const priorLength = arr\n                          .slice(0, pIndex)\n                          .join('').length;\n                        const segments = paragraph.split(/(\\s+)/);\n\n                        return (\n                          <motion.p key={`p-${pIndex}-${currentEntry.id}`}>\n                            {segments.map((segment: string, index: number) => {\n                              if (/\\s+/.test(segment)) {\n                                return (\n                                  <motion.span\n                                    key={index}\n                                    className=\"whitespace-pre\"\n                                  >\n                                    {segment}\n                                  </motion.span>\n                                );\n                              }\n\n                              const previousLengths = segments\n                                .slice(0, index)\n                                .join('').length;\n\n                              return (\n                                <motion.span\n                                  key={`word-${index}-${currentEntry.id}`}\n                                  className=\"inline-block\"\n                                >\n                                  {Array.from(segment).map(\n                                    (char: string, charIndex: number) => {\n                                      const globalIndex =\n                                        priorLength +\n                                        previousLengths +\n                                        charIndex;\n\n                                      return (\n                                        <motion.span\n                                          key={`char-${charIndex}-${currentEntry.id}`}\n                                          custom={globalIndex}\n                                          variants={charVariants}\n                                          initial=\"enter\"\n                                          animate=\"center\"\n                                          exit=\"exit\"\n                                          className=\"relative inline-block\"\n                                        >\n                                          {char}\n                                        </motion.span>\n                                      );\n                                    },\n                                  )}\n                                </motion.span>\n                              );\n                            })}\n                          </motion.p>\n                        );\n                      })}\n                    </motion.div>\n                  ) : (\n                    currentEntry.content\n                  )}\n                </motion.div>\n              </AnimatePresence>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
    }
  ]
}
