From e57a7f36abf3d22b3b786edf6ad6c50a79b614b4 Mon Sep 17 00:00:00 2001 From: Bipin Prajapati Date: Sat, 18 Jul 2026 13:12:22 +0530 Subject: [PATCH] added change log UI --- .../(dashboard-layout)/doc/changelog/page.tsx | 93 ++++++++++++++++ src/components/app-sidebar.tsx | 1 + .../landing/smooth-scroll/smooth-scroll.tsx | 45 +++++++- .../uiable/changelog/changelog-entry.tsx | 99 +++++++++++++++++ .../uiable/changelog/changelog-mobile-toc.tsx | 103 ++++++++++++++++++ .../uiable/changelog/changelog-toc.tsx | 16 +++ src/data/changelog-data.ts | 85 +++++++++++++++ 7 files changed, 439 insertions(+), 3 deletions(-) create mode 100644 src/app/(dashboard-layout)/doc/changelog/page.tsx create mode 100644 src/components/uiable/changelog/changelog-entry.tsx create mode 100644 src/components/uiable/changelog/changelog-mobile-toc.tsx create mode 100644 src/components/uiable/changelog/changelog-toc.tsx create mode 100644 src/data/changelog-data.ts 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"], + }, + ], + }, +]