diff --git a/src/app/(dashboard-layout)/doc/changelog/page.tsx b/src/app/(dashboard-layout)/doc/changelog/page.tsx
new file mode 100644
index 0000000..1ad7642
--- /dev/null
+++ b/src/app/(dashboard-layout)/doc/changelog/page.tsx
@@ -0,0 +1,93 @@
+"use client"
+
+import { useEffect } from "react"
+
+// shadcn
+import { Card, CardContent, CardHeader } from "@/components/ui/card"
+import { Separator } from "@/components/ui/separator"
+
+// project
+import branding from "@/branding.json"
+import DocsNavigation from "@/components/doc-bottom-nav"
+import ChangelogEntry from "@/components/uiable/changelog/changelog-entry"
+import ChangelogMobileToc from "@/components/uiable/changelog/changelog-mobile-toc"
+import ChangelogToc from "@/components/uiable/changelog/changelog-toc"
+import { CHANGELOG_DATA } from "@/data/changelog-data"
+
+// ------------------------------ | PAGE - CHANGELOG | ------------------------------ //
+
+export default function ChangelogPage() {
+ // Deep linking: scroll to anchor from URL hash on mount
+ useEffect(() => {
+ const hash = window.location.hash.replace("#", "")
+ if (hash) {
+ // Allow the DOM to settle first
+ requestAnimationFrame(() => {
+ const element = document.getElementById(hash)
+ if (element) {
+ element.scrollIntoView({ behavior: "smooth" })
+ }
+ })
+ }
+ }, [])
+
+ return (
+
+
+
+
+
+
+
+
Changelog
+
+ Latest updates and announcements for {branding.brandName}.
+ Track new components, improvements, and breaking changes
+ across releases.
+
+
+
+
+
+
+
+
+ {CHANGELOG_DATA.map((release, index) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+ On this page
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx
index eeec82e..14cd6c1 100644
--- a/src/components/app-sidebar.tsx
+++ b/src/components/app-sidebar.tsx
@@ -70,6 +70,7 @@ export function AppSidebar({ ...props }: ComponentProps) {
{ title: "Installation", href: "/doc/installation" },
{ title: "Shadcn CLI", href: "/doc/cli" },
{ title: "Components", href: "/doc/components" },
+ { title: "Changelog", href: "/doc/changelog" },
]
const filteredDocs = docs.filter((doc) =>
diff --git a/src/components/uiable/blocks/landing/smooth-scroll/smooth-scroll.tsx b/src/components/uiable/blocks/landing/smooth-scroll/smooth-scroll.tsx
index 3b3dc7c..f9d12dd 100644
--- a/src/components/uiable/blocks/landing/smooth-scroll/smooth-scroll.tsx
+++ b/src/components/uiable/blocks/landing/smooth-scroll/smooth-scroll.tsx
@@ -1,13 +1,52 @@
"use client"
-import { ReactNode } from "react"
-import { ReactLenis } from "lenis/react"
+import { ReactNode, useEffect, useRef } from "react"
+import { ReactLenis, useLenis } from "lenis/react"
// ------------------------------ | COMPONENT - SMOOTH SCROLL | ------------------------------ //
+const LENIS_OPTIONS = { lerp: 0.1, duration: 2.2 }
+
+function isScrollLocked() {
+ return (
+ document.body.hasAttribute("data-scroll-locked") ||
+ document.body.style.overflow === "hidden"
+ )
+}
+
+function ScrollLocker() {
+ const lenis = useLenis()
+ const lockedRef = useRef(false)
+
+ useEffect(() => {
+ if (!lenis) return
+
+ const applyState = () => {
+ const locked = isScrollLocked()
+ if (locked === lockedRef.current) return
+ lockedRef.current = locked
+ locked ? lenis.stop() : lenis.start()
+ }
+
+ const observer = new MutationObserver(applyState)
+
+ observer.observe(document.body, {
+ attributes: true,
+ attributeFilter: ["data-scroll-locked", "style"],
+ })
+
+ applyState()
+
+ return () => observer.disconnect()
+ }, [lenis])
+
+ return null
+}
+
export default function SmoothScroll({ children }: { children: ReactNode }) {
return (
-
+
+
{children}
)
diff --git a/src/components/uiable/changelog/changelog-entry.tsx b/src/components/uiable/changelog/changelog-entry.tsx
new file mode 100644
index 0000000..daf49a0
--- /dev/null
+++ b/src/components/uiable/changelog/changelog-entry.tsx
@@ -0,0 +1,99 @@
+"use client"
+
+// shadcn
+import { Separator } from "@/components/ui/separator"
+import { ExternalLink } from "lucide-react"
+
+// project
+import { CHANGELOG_DATA } from "@/data/changelog-data"
+
+// types
+type ChangelogRelease = (typeof CHANGELOG_DATA)[number]
+
+interface ChangelogEntryProps {
+ release: ChangelogRelease
+ isLast?: boolean
+}
+
+// ------------------------------ | COMPONENT - CHANGELOG ENTRY | ------------------------------ //
+
+export default function ChangelogEntry({
+ release,
+ isLast = false,
+}: ChangelogEntryProps) {
+ return (
+
+
+
+
+
+ {release.categories.map((category) => (
+
+
+
+ {category.title}
+
+
+ {category.items.length}{" "}
+ {category.items.length === 1 ? "change" : "changes"}
+
+
+
+ {category.items.map((item, index) => {
+ const text = typeof item === "string" ? item : item.text
+ const previewUrl =
+ typeof item === "string" ? undefined : item.previewUrl
+
+ return (
+ -
+ {text}
+ {previewUrl && (
+
+ Preview
+
+
+ )}
+
+ )
+ })}
+
+
+ ))}
+
+
+
+ {!isLast && }
+
+ )
+}
diff --git a/src/components/uiable/changelog/changelog-mobile-toc.tsx b/src/components/uiable/changelog/changelog-mobile-toc.tsx
new file mode 100644
index 0000000..6643351
--- /dev/null
+++ b/src/components/uiable/changelog/changelog-mobile-toc.tsx
@@ -0,0 +1,103 @@
+"use client"
+
+import { useEffect, useState } from "react"
+
+// shadcn
+import { Button } from "@/components/ui/button"
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+ SheetTrigger,
+} from "@/components/ui/sheet"
+
+// project
+import { cn } from "@/lib/utils"
+import { CHANGELOG_DATA } from "@/data/changelog-data"
+
+// assets
+import { ListTree } from "lucide-react"
+
+// ------------------------------ | COMPONENT - CHANGELOG MOBILE TOC | ------------------------------ //
+
+export default function ChangelogMobileToc() {
+ const [activeId, setActiveId] = useState(null)
+ const [open, setOpen] = useState(false)
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ setActiveId(entry.target.id)
+ }
+ })
+ },
+ { rootMargin: "0% 0% -80% 0%" }
+ )
+
+ CHANGELOG_DATA.forEach((release) => {
+ const element = document.getElementById(release.anchor)
+ if (element) observer.observe(element)
+ })
+
+ return () => observer.disconnect()
+ }, [])
+
+ const handleClick = (anchor: string) => {
+ setOpen(false)
+ // Small delay to let sheet close before scrolling
+ requestAnimationFrame(() => {
+ const element = document.getElementById(anchor)
+ if (element) {
+ element.scrollIntoView({ behavior: "smooth" })
+ }
+ })
+ }
+
+ return (
+
+
+
+ }
+ >
+
+ On this page
+
+
+
+ On this page
+
+
+
+
+
+ )
+}
diff --git a/src/components/uiable/changelog/changelog-toc.tsx b/src/components/uiable/changelog/changelog-toc.tsx
new file mode 100644
index 0000000..aaa6fbe
--- /dev/null
+++ b/src/components/uiable/changelog/changelog-toc.tsx
@@ -0,0 +1,16 @@
+"use client"
+
+// project
+import TableOfContents from "@/components/uiable/layout/table-of-contents"
+import { CHANGELOG_DATA } from "@/data/changelog-data"
+
+// ------------------------------ | COMPONENT - CHANGELOG TOC | ------------------------------ //
+
+export default function ChangelogToc() {
+ const tocItems = CHANGELOG_DATA.map((release) => ({
+ title: `v${release.version} - ${release.date}`,
+ url: `#${release.anchor}`,
+ }))
+
+ return
+}
diff --git a/src/data/changelog-data.ts b/src/data/changelog-data.ts
new file mode 100644
index 0000000..6db1815
--- /dev/null
+++ b/src/data/changelog-data.ts
@@ -0,0 +1,85 @@
+interface ChangelogItem {
+ text: string
+ previewUrl?: string
+}
+
+interface ChangelogCategory {
+ title: string
+ items: (string | ChangelogItem)[]
+}
+
+interface ChangelogRelease {
+ version: string
+ date: string
+ title?: string
+ anchor: string
+ categories: ChangelogCategory[]
+}
+
+// ------------------------------ | DATA - CHANGELOG | ------------------------------ //
+
+export const CHANGELOG_DATA: ChangelogRelease[] = [
+ {
+ version: "1.2.0",
+ date: "July 14, 2026",
+ anchor: "v1-2-0",
+ categories: [
+ {
+ title: "New Components variants",
+ items: [
+ { text: "Command: 4 new variants", previewUrl: "/components/command" },
+ { text: "Date Picker: 2 new variants (Disabled, Time)", previewUrl: "/components/date-picker" },
+ { text: "Input: 3 new variants (Range, Select, Validation)", previewUrl: "/components/input" },
+ { text: "Input Group: 1 new variant (Chat Message)", previewUrl: "/components/input-group" },
+ { text: "Input OTP: 3 new variants (Animated, Filled, Outlined)", previewUrl: "/components/input-otp" },
+ { text: "Item: 4 new variants (Background, Checkbox, Separator, Switch)", previewUrl: "/components/item" },
+ { text: "Message Scroller: 3 core variants (Anchoring Turns, Animating, Context Visible)", previewUrl: "/components/message-scroller" },
+ { text: "Radio Group: 4 new variants (Box, Colors, List, Sizes)", previewUrl: "/components/radio-group" },
+ ],
+ },
+ {
+ title: "Introduce New UI Blocks",
+ items: [
+ { text: "Call To Action", previewUrl: "/blocks/call-to-action" },
+ { text: "Contact", previewUrl: "/blocks/contact" },
+ { text: "Content", previewUrl: "/blocks/content" },
+ { text: "FAQ", previewUrl: "/blocks/faq" },
+ ],
+ },
+ ],
+ },
+ {
+ version: "1.1.0",
+ date: "July 6, 2026",
+ anchor: "v1-1-0",
+ categories: [
+ {
+ title: "New Components",
+ items: [
+ { text: "Attachment", previewUrl: "/components/attachment" },
+ { text: "Bubble", previewUrl: "/components/bubble" },
+ { text: "Marker", previewUrl: "/components/marker" },
+ { text: "Message", previewUrl: "/components/message" },
+ { text: "MessageScroller", previewUrl: "/components/message-scroller" },
+ ],
+ },
+ {
+ title: "Enhanced",
+ items: [
+ "Added new button, button group, checkbox, and combobox variants.",
+ ],
+ },
+ ],
+ },
+ {
+ version: "1.0.0",
+ date: "July 4, 2026",
+ anchor: "v1-0-0",
+ categories: [
+ {
+ title: "Initial Release",
+ items: ["Initial Version laying Foundation for library"],
+ },
+ ],
+ },
+]