From 73f0a12a1f51c03e31d28c9516922472a24d5bc9 Mon Sep 17 00:00:00 2001 From: Pranay Kothapalli Date: Mon, 13 Apr 2026 12:35:59 +0530 Subject: [PATCH 1/5] feat(docs): add paginated changelog page from CHANGELOG.md - Parse root CHANGELOG.md via existing getSourceCodeFromPath (local + raw GitHub on Vercel) - Show five releases per page with Previous/Next and npm link per version - Add Changelog under First Steps in docs nav; GitHub edit link targets CHANGELOG.md --- docs/app/docs/docsNavigationSections.tsx | 4 + .../changelog/ChangelogMarkdown.tsx | 85 ++++++++ docs/app/docs/first-steps/changelog/page.tsx | 184 ++++++++++++++++++ .../docsHelpers/EditPageOnGithub.tsx | 27 ++- docs/package.json | 1 + docs/pnpm-lock.yaml | 32 +++ docs/utils/changelog/parseChangelog.ts | 29 +++ 7 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 docs/app/docs/first-steps/changelog/ChangelogMarkdown.tsx create mode 100644 docs/app/docs/first-steps/changelog/page.tsx create mode 100644 docs/utils/changelog/parseChangelog.ts diff --git a/docs/app/docs/docsNavigationSections.tsx b/docs/app/docs/docsNavigationSections.tsx index f49f4791f..a306dfe8a 100644 --- a/docs/app/docs/docsNavigationSections.tsx +++ b/docs/app/docs/docsNavigationSections.tsx @@ -14,6 +14,10 @@ export const docsNavigationSections = [ { title:"Usage", path:"/docs/first-steps/usage" + }, + { + title:"Changelog", + path:"/docs/first-steps/changelog" } ] }, diff --git a/docs/app/docs/first-steps/changelog/ChangelogMarkdown.tsx b/docs/app/docs/first-steps/changelog/ChangelogMarkdown.tsx new file mode 100644 index 000000000..a0ffbe661 --- /dev/null +++ b/docs/app/docs/first-steps/changelog/ChangelogMarkdown.tsx @@ -0,0 +1,85 @@ +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; + +type ChangelogMarkdownProps = { + markdown: string; +}; + +/** + * Renders a single release section from CHANGELOG.md (GFM: lists, nested headings, fenced code). + */ +export function ChangelogMarkdown({ markdown }: ChangelogMarkdownProps) { + return ( + ( +

+ {children} +

+ ), + h2: ({ children }) => ( +

+ {children} +

+ ), + h3: ({ children }) => ( +
+ {children} +
+ ), + p: ({ children }) => ( +

{children}

+ ), + ul: ({ children }) => ( + + ), + ol: ({ children }) => ( +
    {children}
+ ), + li: ({ children }) =>
  • {children}
  • , + pre: ({ children }) => ( +
    +                        {children}
    +                    
    + ), + code: ({ className, children, ...props }) => { + const isBlock = Boolean(className?.includes("language-")); + if (isBlock) { + return ( + + {children} + + ); + } + return ( + + {children} + + ); + }, + a: ({ href, children }) => ( + + {children} + + ), + strong: ({ children }) => ( + {children} + ), + }} + > + {markdown} +
    + ); +} diff --git a/docs/app/docs/first-steps/changelog/page.tsx b/docs/app/docs/first-steps/changelog/page.tsx new file mode 100644 index 000000000..25d2d8a9f --- /dev/null +++ b/docs/app/docs/first-steps/changelog/page.tsx @@ -0,0 +1,184 @@ +import Link from "next/link"; + +import { ChangelogMarkdown } from "./ChangelogMarkdown"; +import { getSourceCodeFromPath } from "@/utils/parseSourceCode"; +import { + CHANGELOG_PAGE_SIZE, + parseChangelogMarkdown, +} from "@/utils/changelog/parseChangelog"; +import generateSeoMetadata from "@/utils/seo/generateSeoMetadata"; + +const SITE_URL = process.env.SITE_URL ?? "https://www.rad-ui.com"; +const CHANGELOG_PATH = "/docs/first-steps/changelog"; +const NPM_VERSION = (v: string) => + `https://www.npmjs.com/package/@radui/ui/v/${encodeURIComponent(v)}`; + +const baseMetadata = generateSeoMetadata({ + title: "Changelog", + description: + "Release history for @radui/ui: minor and patch changes shipped in each published version.", + keywords: ["changelog", "release notes", "@radui/ui", "npm", "versions"], + canonicalUrl: `${SITE_URL}${CHANGELOG_PATH}`, +}); + +export async function generateMetadata({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}) { + const sp = await searchParams; + const pageNum = parseChangelogPage(sp.page); + const titleSuffix = pageNum > 1 ? ` (page ${pageNum})` : ""; + const canonical = + pageNum > 1 + ? `${SITE_URL}${CHANGELOG_PATH}?page=${pageNum}` + : `${SITE_URL}${CHANGELOG_PATH}`; + + return { + ...baseMetadata, + title: `Changelog${titleSuffix} | Rad UI`, + alternates: { + ...baseMetadata.alternates, + canonical, + }, + openGraph: { + ...baseMetadata.openGraph, + title: `Changelog${titleSuffix}`, + url: canonical, + }, + twitter: { + ...baseMetadata.twitter, + title: `Changelog${titleSuffix}`, + }, + }; +} + +function parseChangelogPage(raw: string | undefined): number { + const n = parseInt(raw ?? "1", 10); + if (Number.isNaN(n) || n < 1) return 1; + return n; +} + +export default async function ChangelogPage({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}) { + const sp = await searchParams; + const pageNum = parseChangelogPage(sp.page); + const raw = await getSourceCodeFromPath("CHANGELOG.md"); + const releases = parseChangelogMarkdown(raw); + const totalPages = Math.max(1, Math.ceil(releases.length / CHANGELOG_PAGE_SIZE)); + const safePage = Math.min(pageNum, totalPages); + const offset = (safePage - 1) * CHANGELOG_PAGE_SIZE; + const pageReleases = releases.slice(offset, offset + CHANGELOG_PAGE_SIZE); + + return ( +
    +
    +

    + Changelog +

    +

    + Published versions of{" "} + + @radui/ui + + . Minor and patch sections follow{" "} + + SemVer + + . Source:{" "} + + CHANGELOG.md + {" "} + in the repository. +

    +
    + + {releases.length === 0 ? ( +

    + No releases found in CHANGELOG.md. +

    + ) : null} + +
    + {pageReleases.map((release) => ( + + ))} +
    + + {totalPages > 1 ? ( + + ) : null} +
    + ); +} diff --git a/docs/components/docsHelpers/EditPageOnGithub.tsx b/docs/components/docsHelpers/EditPageOnGithub.tsx index 20291f1eb..57e50032a 100644 --- a/docs/components/docsHelpers/EditPageOnGithub.tsx +++ b/docs/components/docsHelpers/EditPageOnGithub.tsx @@ -3,19 +3,34 @@ import { usePathname } from "next/navigation"; import Link from "@radui/ui/Link"; -const EditPageOnGithub = () => { +const CHANGELOG_EDIT_HREF = + "https://github.com/rad-ui/ui/edit/main/CHANGELOG.md"; +const EditPageOnGithub = () => { const pathname = usePathname(); const page = pathname.split("/").slice(2).join("/"); + if (page === "first-steps/changelog") { + return ( +
    + + Edit changelog on GitHub + +
    + ); + } const currentDocsPath = "docs/app/docs/" + page; - return ( -
    - Edit this page on GitHub -
    - ); + return ( +
    + + Edit this page on GitHub + +
    + ); }; export default EditPageOnGithub; \ No newline at end of file diff --git a/docs/package.json b/docs/package.json index 9d3d29c7b..36741287e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -37,6 +37,7 @@ "posthog-node": "^5.6.0", "react": "19.0.0", "react-dom": "19.0.0", + "react-markdown": "^10.1.0", "refractor": "^4.8.1", "remark-gfm": "^4.0.1", "sass": "^1.70.0", diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 35e8ad4f2..d89799575 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: react-dom: specifier: 19.0.0 version: 19.0.0(react@19.0.0) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.0.2)(react@19.0.0) refractor: specifier: ^4.8.1 version: 4.8.1 @@ -1456,6 +1459,9 @@ packages: resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} engines: {node: '>=6'} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2199,6 +2205,12 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -4311,6 +4323,8 @@ snapshots: hex-rgb@4.3.0: {} + html-url-attributes@3.0.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -5281,6 +5295,24 @@ snapshots: react-is@16.13.1: {} + react-markdown@10.1.0(@types/react@19.0.2)(react@19.0.0): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.0.2 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.3 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.0 + react: 19.0.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.1 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-remove-scroll-bar@2.3.8(@types/react@19.0.2)(react@19.0.0): dependencies: react: 19.0.0 diff --git a/docs/utils/changelog/parseChangelog.ts b/docs/utils/changelog/parseChangelog.ts new file mode 100644 index 000000000..fa221bd5b --- /dev/null +++ b/docs/utils/changelog/parseChangelog.ts @@ -0,0 +1,29 @@ +export type ChangelogRelease = { + version: string; + /** Markdown body under the version heading (Minor/Patch sections and bullets). */ + body: string; +}; + +/** + * Split root CHANGELOG.md (Changesets format) into one entry per `## version` section. + */ +export function parseChangelogMarkdown(raw: string): ChangelogRelease[] { + const trimmed = raw.trimStart(); + const withoutTitle = trimmed.replace(/^#\s+[^\n]*\n+/, ""); + const chunks = withoutTitle + .split(/(?=^##\s+)/m) + .map((c) => c.trim()) + .filter(Boolean); + + return chunks.map((chunk) => { + const firstNewline = chunk.indexOf("\n"); + const headerLine = firstNewline === -1 ? chunk : chunk.slice(0, firstNewline); + const m = headerLine.match(/^##\s+(.+)$/); + const version = m ? m[1].trim() : "unknown"; + const body = + firstNewline === -1 ? "" : chunk.slice(firstNewline + 1).trim(); + return { version, body }; + }); +} + +export const CHANGELOG_PAGE_SIZE = 5; From 67fc024ccb2e0398e5e4361669b7b8e8e462cbe1 Mon Sep 17 00:00:00 2001 From: Pranay Kothapalli Date: Mon, 13 Apr 2026 12:39:08 +0530 Subject: [PATCH 2/5] add: prompt + condense release notes --- CHANGELOG.md | 119 +++---------------------- agents/security/fix-vulnerabilities.md | 25 ++++++ 2 files changed, 37 insertions(+), 107 deletions(-) create mode 100644 agents/security/fix-vulnerabilities.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f33b752ce..af9320a17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,23 +4,17 @@ ### Minor Changes -- d2c5395: migration from querySelector to refs for rovingfoucs, tree and slider +- d2c5395: migration from querySelector to refs for roving focus, tree and slider ### Patch Changes - 4db8aa7: ui revamp for multiple comps -## 0.2.2 - -### Patch Changes - -- 83cf51c: Update GitHub Actions workflows to Node 24 and newer GitHub action versions for release, test, lint, coverage, and Chromatic jobs. - ## 0.2.1 ### Patch Changes -- 9a28add: Align AlertDialog styling with theme tokens, and clean up Storybook examples and local dev startup behavior. +- 9a28add: Align AlertDialog styling with theme tokens. ## 0.2.0 @@ -36,7 +30,6 @@ - 38503b6: mergeRefs added - b968b4a: export types for all comps - a42620d: Fix select and combobox popup behavior by improving portal rendering, restoring macOS-style reopen anchoring for `Select`, and tightening related UI polish in the sandbox and shared component styles. -- 3e892dd: Refactored internal data attribute utilities from hook-style factories to plain functions and updated shared component usage. ## 0.1.10 @@ -57,27 +50,13 @@ - d22ef40: resize, size and varaint api support added for textarea -## 0.1.7 - -### Patch Changes - -- 75152f8: Fix GitHub workflows to use correct build script instead of silent failure - ## 0.1.6 ### Patch Changes - 4363ff3: Added Separator for dropdown , context and menubar menus - 336fe3f: MenuPrimitive root now supports rtl, loop, avoidCollision,placement and the item supports disabled, asChild , onSelect. Tests for the same have been added too. -- bcad222: ## Fix: Missing Components in NPM Bundle - - **Versions 0.1.0 - 0.1.5** had incomplete component bundles due to build failures. - - **Root Cause**: Due to updates we've made to build processes to build components for npm, we might have broken the process due to list on package.json not being in sync, we totally missed testing a couple of versions. - - **Fix**: Increased Node.js heap size to 8GB and added export validation to prevent silent build failures. - - **New Features**: Added `--check` flag for export validation, ESM/CommonJS support, and root export support. +- bcad222: Fix incomplete npm bundles for some 0.1.x releases; raise Node heap for builds, add export validation and a `--check` flag, with ESM/CJS and root export support. ## 0.1.5 @@ -85,94 +64,20 @@ - f77dc1b: Sub-components, now throw subtle warnings instead of console logs -## 0.1.4 - -### Patch Changes - -- 6b514f8: fix formatting of announcement - test workflow -- 6b514f8: Patch workflow indentation issue - -## 0.1.3 - -### Patch Changes - -- cb1205d: Patch workflow indentation issue -- 2d19c42: Testing Discord Release Workflow - -## 0.1.2 - -### Patch Changes - -- e394a8d: testing changeset workflow automation - part 2 - -## 0.1.1 - -### Patch Changes - -- 759dcc6: Test patch to check workflows - ## 0.1.0 ### Minor Changes -- b471ddb: This release adds tests around the codebase, improves API support across multiple components, introduces new features like Steps + Minimap and roving focus in CheckboxGroups, while cleaning up builds, docs, and accessibility. - - ### ✨ Features - - - Added **roving focus support for CheckboxGroup**. - - Improved **RadioGroup behavior and accessibility**. - - Enhanced **Select component behavior**. - - Introduced **Steps + Minimap basic implementation**. - - Refactored multiple components to support **`forwardRef`**: - - Avatar, Badge, BlockQuote, Card, Code, Collapsible, ContextMenu, DataList, RadioGroup, Splitter, Table, Tabs, ToggleGroup, VisuallyHidden. - - ### 🧪 Tests & Accessibility - - - Expanded **a11y test coverage**: axe-core, Accordion, Slider, RadioGroup, AlertDialog, Dialog, Primitive `asChild`, and SSR hydration scenarios. - - Added **portal test utilities** for Dialog/AlertDialog. - - Stabilized and silenced noisy test warnings. - - Enforced **global test coverage thresholds**. - - Parallelized Jest test runs with **sharding** for faster feedback. - - ### 🛠 Fixes & Refactors - - - Fixed `SelectPrimitiveItemProps` export typing. - - Refined **NavigationMenu typing**. - - Increased **Rollup build memory limit** and excluded Storybook files from builds. - - Silenced “Primitive `asChild`” warnings in tests. - - ### 📚 Docs & Chores - - - Added **CodeRabbit sponsor hero** to docs. - - Added **Changeset workflow** for release management. - - Improved **WCAG tree navigation support**. - - Minor styling fixes and dark mode improvements. - - Updated Toggle API: `onChange` → `onPressedChange`. - - ## BREAKING CHANGES - - ### Toggle Component API Rename - - The Toggle component's `onChange` prop has been renamed to `onPressedChange` to better reflect its semantic meaning and align with accessibility standards. - - **Migration Required:** - - ```tsx - // Before - { /* handle toggle */ }} /> - - // After - { /* handle toggle */ }} /> - ``` +- b471ddb: Tests, API improvements across components, Steps + Minimap, roving focus for CheckboxGroup, RadioGroup and Select updates, `forwardRef` on many primitives, a11y and build fixes. - **TypeScript Updates:** +### BREAKING CHANGES - - Update any TypeScript interfaces or type definitions that reference `onChange` to use `onPressedChange` - - The prop signature remains the same: `(pressed: boolean) => void` - - Update any custom Toggle wrapper components or higher-order components that pass through the `onChange` prop +**Toggle:** `onChange` was renamed to `onPressedChange`. - **Impact:** +```tsx +// Before + { /* handle toggle */ }} /> - - This change affects all consumers using the Toggle component - - Update your codebase to use the new `onPressedChange` prop name - - No functional changes to the component behavior - only the prop name has changed +// After + { /* handle toggle */ }} /> +``` diff --git a/agents/security/fix-vulnerabilities.md b/agents/security/fix-vulnerabilities.md new file mode 100644 index 000000000..10f88af00 --- /dev/null +++ b/agents/security/fix-vulnerabilities.md @@ -0,0 +1,25 @@ +You are working in this repo’s local clone. Goal: one integration branch that brings all dependency and security-related updates together so we can merge confidently. + +Scope + +Link to all open PRs - + +Treat open PRs from Dependabot, Snyk, Renovate, and similar automated dependency/security bots as the source of truth for what to bump (versions, packages, and which workspaces: root vs subfolders with their own package.json). +Apply equivalent changes on a single new branch off the appropriate base (usually main), instead of merging many small PRs one by one—unless I say otherwise. +Requirements + +Inventory: Summarize which packages/workspaces those bot PRs target and the version bumps they propose (group by root vs subproject). + +Implement: Update package.json (and lockfiles) consistently so dependency trees are up to date and aligned with those bumps; resolve conflicts and avoid partial upgrades that leave inconsistent peers. + +Verify: Run the project’s install, build, lint, and test commands (whatever this repo documents in package.json scripts / CI) until they pass for all relevant workspaces. + +Safety: Run npm audit / pnpm audit / yarn npm audit (whichever the repo uses) and address high/critical issues where reasonable; note anything that needs a breaking major or cannot be fixed without product decisions. +Deliverable: Short summary of what changed, remaining risks, and suggested PR title/description. Do not change unrelated code or docs unless required for the upgrade. +Constraints + +Prefer minimal, focused diffs; no drive-by refactors. +If gh or network access to GitHub fails, infer from package.json + lockfiles + audit and still produce a coherent upgrade plan. +Start only when I say “go” (or equivalent). + +Once all the relevan changes are done, as a summary - let the user know what all PRs can be closed, what are pending and what arent addressed with relevant details and blockers if any. From 6a8ec173695a35145191084cac93e2e40e12802b Mon Sep 17 00:00:00 2001 From: Pranay Kothapalli Date: Mon, 13 Apr 2026 13:16:24 +0530 Subject: [PATCH 3/5] add: changelog + other minor improvements --- agents/documentation/building-ui.md | 0 .../changelog/ChangelogMarkdown.tsx | 140 ++++++++-- .../changelog/highlightChangelogCode.tsx | 65 +++++ docs/app/docs/first-steps/changelog/page.tsx | 45 +++- docs/app/docs/layout.tsx | 41 --- docs/app/globals.scss | 239 ++++++++++++++++-- docs/app/layout.js | 4 +- .../Documentation/DocsTableOfContents.tsx | 6 +- .../layout/Documentation/helpers/CodeBlock.js | 21 +- docs/components/layout/Documentation/utils.js | 2 +- docs/components/navigation/NavItem.js | 2 +- 11 files changed, 452 insertions(+), 113 deletions(-) create mode 100644 agents/documentation/building-ui.md create mode 100644 docs/app/docs/first-steps/changelog/highlightChangelogCode.tsx diff --git a/agents/documentation/building-ui.md b/agents/documentation/building-ui.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/app/docs/first-steps/changelog/ChangelogMarkdown.tsx b/docs/app/docs/first-steps/changelog/ChangelogMarkdown.tsx index a0ffbe661..b13da06c9 100644 --- a/docs/app/docs/first-steps/changelog/ChangelogMarkdown.tsx +++ b/docs/app/docs/first-steps/changelog/ChangelogMarkdown.tsx @@ -1,10 +1,96 @@ +import { + cloneElement, + isValidElement, + type ReactElement, + type ReactNode, +} from "react"; +import clsx from "clsx"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; +import { highlightChangelogCode } from "./highlightChangelogCode"; + type ChangelogMarkdownProps = { markdown: string; }; +const COMMIT_PREFIX = /^([a-f0-9]{7}):\s*/i; + +function headingPlainText(children: ReactNode): string { + if (children == null) return ""; + if (typeof children === "string" || typeof children === "number") { + return String(children); + } + if (Array.isArray(children)) { + return children.map(headingPlainText).join(""); + } + if (isValidElement(children)) { + const el = children as ReactElement<{ children?: ReactNode }>; + return headingPlainText(el.props.children); + } + return ""; +} + +function sectionHeadingStyles(label: string): string { + const l = label.toLowerCase(); + /** Tinted bar + near-black text for contrast on pale backgrounds */ + if (l.includes("breaking")) { + return "border-red-600 bg-red-100/90 text-gray-950"; + } + if (l.includes("minor")) { + return "border-red-500 bg-red-100/90 text-gray-950"; + } + if (l.includes("patch")) { + return "border-blue-600 bg-blue-100/90 text-gray-950"; + } + return "border-gray-400 bg-gray-100 text-gray-950"; +} + +/** + * Turns the leading `abc1234: ` on a list line into a monospace pill; works when + * remark splits inline marks (e.g. `hash: **Bold**:`) across several nodes. + */ +function peelLeadingCommitHash(node: ReactNode): ReactNode { + if (typeof node === "string") { + const m = node.match(COMMIT_PREFIX); + if (m) { + const rest = node.slice(m[0].length); + return ( + <> + + {m[1]} + + {rest} + + ); + } + return node; + } + if (Array.isArray(node)) { + if (node.length === 0) return node; + const [first, ...rest] = node; + return ( + <> + {peelLeadingCommitHash(first)} + {rest} + + ); + } + if (isValidElement(node)) { + const el = node as ReactElement<{ children?: ReactNode }>; + if (el.props.children != null) { + return cloneElement(el, { + ...el.props, + children: peelLeadingCommitHash(el.props.children), + }); + } + } + return node; +} + /** * Renders a single release section from CHANGELOG.md (GFM: lists, nested headings, fenced code). */ @@ -14,50 +100,70 @@ export function ChangelogMarkdown({ markdown }: ChangelogMarkdownProps) { remarkPlugins={[remarkGfm]} components={{ h1: ({ children }) => ( -

    +

    {children}

    ), h2: ({ children }) => ( -

    +

    {children}

    ), - h3: ({ children }) => ( -
    - {children} -
    - ), + h3: ({ children }) => { + const label = headingPlainText(children); + const bar = sectionHeadingStyles(label); + return ( +
    + {children} +
    + ); + }, p: ({ children }) => ( -

    {children}

    +

    {children}

    ), ul: ({ children }) => ( -
      {children}
    +
      + {children} +
    ), ol: ({ children }) => ( -
      {children}
    +
      + {children} +
    + ), + li: ({ children }) => ( +
  • + {peelLeadingCommitHash(children)} +
  • ), - li: ({ children }) =>
  • {children}
  • , pre: ({ children }) => ( -
    +                    
                             {children}
                         
    ), code: ({ className, children, ...props }) => { const isBlock = Boolean(className?.includes("language-")); if (isBlock) { + const lang = + className?.match(/language-([\w-]+)/)?.[1] ?? "tsx"; + const source = String(children ?? ""); return ( - {children} + {highlightChangelogCode(source, lang)} ); } return ( {children} @@ -66,7 +172,7 @@ export function ChangelogMarkdown({ markdown }: ChangelogMarkdownProps) { }, a: ({ href, children }) => ( ), strong: ({ children }) => ( - {children} + {children} ), }} > diff --git a/docs/app/docs/first-steps/changelog/highlightChangelogCode.tsx b/docs/app/docs/first-steps/changelog/highlightChangelogCode.tsx new file mode 100644 index 000000000..774f19b7a --- /dev/null +++ b/docs/app/docs/first-steps/changelog/highlightChangelogCode.tsx @@ -0,0 +1,65 @@ +import React, { type ReactNode } from "react"; +import { refractor } from "refractor"; +import bash from "refractor/lang/bash"; +import js from "refractor/lang/javascript"; +import jsx from "refractor/lang/jsx"; +import ts from "refractor/lang/typescript"; +import tsx from "refractor/lang/tsx"; + +let registered = false; + +function ensureLanguagesRegistered() { + if (registered) return; + refractor.register(js); + refractor.register(jsx); + refractor.register(ts); + refractor.register(tsx); + refractor.register(bash); + registered = true; +} + +type HastElement = { + type: "element"; + tagName: string; + properties: { className?: string[] }; + children: HastChild[]; +}; + +type HastText = { type: "text"; value: string }; + +type HastChild = HastElement | HastText; + +function renderHastNode(element: HastChild, index: number): ReactNode { + if (element.type === "element") { + const { tagName, properties, children } = element; + const className = (properties.className ?? []).join(" "); + return React.createElement( + tagName, + { className, key: index }, + children.map((child, childIndex) => renderHastNode(child, childIndex)), + ); + } + if (element.type === "text") { + return element.value; + } + return null; +} + +/** + * Syntax-highlight markdown fenced code using the same Refractor + token CSS as docs CodeBlock. + */ +export function highlightChangelogCode( + source: string, + language: string, +): ReactNode { + ensureLanguagesRegistered(); + const trimmed = source.replace(/\n$/, ""); + try { + const tree = refractor.highlight(trimmed, language); + return tree.children.map((child, i) => + renderHastNode(child as HastChild, i), + ); + } catch { + return trimmed; + } +} diff --git a/docs/app/docs/first-steps/changelog/page.tsx b/docs/app/docs/first-steps/changelog/page.tsx index 25d2d8a9f..552163512 100644 --- a/docs/app/docs/first-steps/changelog/page.tsx +++ b/docs/app/docs/first-steps/changelog/page.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { ExternalLink } from "lucide-react"; import { ChangelogMarkdown } from "./ChangelogMarkdown"; import { getSourceCodeFromPath } from "@/utils/parseSourceCode"; @@ -10,8 +11,16 @@ import generateSeoMetadata from "@/utils/seo/generateSeoMetadata"; const SITE_URL = process.env.SITE_URL ?? "https://www.rad-ui.com"; const CHANGELOG_PATH = "/docs/first-steps/changelog"; -const NPM_VERSION = (v: string) => - `https://www.npmjs.com/package/@radui/ui/v/${encodeURIComponent(v)}`; + +/** https://www.npmjs.com/package/@radui/ui */ +const NPM_PACKAGE_URL = "https://www.npmjs.com/package/@radui/ui"; + +function npmPackageVersionUrl(version: string) { + return `${NPM_PACKAGE_URL}/v/${encodeURIComponent(version)}`; +} + +/** Alias for older call sites / tooling that still references this name. */ +const NPM_VERSION = npmPackageVersionUrl; const baseMetadata = generateSeoMetadata({ title: "Changelog", @@ -76,17 +85,17 @@ export default async function ChangelogPage({ return (
    -

    +

    Changelog

    -

    +

    Published versions of{" "} - + @radui/ui . Minor and patch sections follow{" "} . Source:{" "} -

    -

    - {release.version} -

    +
    diff --git a/docs/app/docs/layout.tsx b/docs/app/docs/layout.tsx index 17d08ee60..0e6abc429 100644 --- a/docs/app/docs/layout.tsx +++ b/docs/app/docs/layout.tsx @@ -6,11 +6,6 @@ import EditPageOnGithub from "@/components/docsHelpers/EditPageOnGithub"; import DocsTableOfContents from "@/components/layout/Documentation/DocsTableOfContents"; import ScrollArea from "@radui/ui/ScrollArea" -import Callout from "@radui/ui/Callout" -import Heading from "@radui/ui/Heading" -import Link from "@radui/ui/Link"; -import { Wrench } from "lucide-react"; - type Doc = { children: React.ReactNode; }; @@ -32,7 +27,6 @@ const Layout = ({ children }: Doc) => {
    -
    {children}
    @@ -52,10 +46,6 @@ const Layout = ({ children }: Doc) => { ) } -const DocsLayoutChunks = ({ children }: { children: React.ReactNode }) => { - return null; -} - const DocsLayoutGridRoot = ({ children }: { children: React.ReactNode }) => { return
    @@ -68,35 +58,4 @@ const DocsLayoutGridRoot = ({ children }: { children: React.ReactNode }) => {
    } -const DocsLayoutJoinDiscordCallout = () => { - return
    - - - - -
    - - Under Construction - - - We're actively working on new components and features. Stay tuned! Head over to our GitHub to see what's coming next. - - - Let’s build together on our{" "} - - Discord - - . - -
    -
    - -
    -} - - -// DocsLayoutChunks.Root = DocsLayoutGridRoot; -// DocsLayoutChunks.displayName = "DocsLayoutChunks"; - - export default Layout; diff --git a/docs/app/globals.scss b/docs/app/globals.scss index 72f3804b5..447578082 100644 --- a/docs/app/globals.scss +++ b/docs/app/globals.scss @@ -23,7 +23,6 @@ samp { } .docs-code-block { - color: var(--rad-ui-text-primary); font-size: 0.84rem; line-height: 1.72; } @@ -37,63 +36,240 @@ samp { box-shadow: none; } -pre code { - padding: 0; +/* Fenced code / CodeBlock: panel + tokens follow Theme ([data-rad-ui-theme]) */ +pre.docs-syntax-pre code.docs-code-block { border: 0; border-radius: 0; background: transparent; display: block; + padding: 0.875rem 1rem; font-size: 12px; line-height: 1.7; } -code .string { - color: var(--rad-ui-color-green-900); +[data-rad-ui-theme="light"] .docs-syntax-pre { + background-color: var(--rad-ui-surface-panel); + border-color: var(--rad-ui-border-soft); + color: var(--rad-ui-text-primary); + box-shadow: 0 16px 40px color-mix(in oklab, var(--rad-ui-color-gray-1000) 12%, transparent); } -code .punctuation { - color: var(--rad-ui-text-muted); +[data-rad-ui-theme="light"] .docs-syntax-pre .docs-code-block { + color: var(--rad-ui-text-primary); } -code .keyword { - color: var(--rad-ui-color-amber-900); +[data-rad-ui-theme="light"] .docs-syntax-pre code .keyword { + color: var(--rad-ui-color-red-900); } -code .function, -.function-variable { - color: var(--rad-ui-color-blue-900); +[data-rad-ui-theme="light"] .docs-syntax-pre code .string, +[data-rad-ui-theme="light"] .docs-syntax-pre code .attr-value, +[data-rad-ui-theme="light"] .docs-syntax-pre code .template-string, +[data-rad-ui-theme="light"] .docs-syntax-pre code .inserted { + color: var(--rad-ui-color-green-900); } -code .class-name { - color: var(--rad-ui-color-blue-900); +[data-rad-ui-theme="light"] .docs-syntax-pre code .number, +[data-rad-ui-theme="light"] .docs-syntax-pre code .boolean { + color: var(--rad-ui-color-green-900); } -code .tag { +[data-rad-ui-theme="light"] .docs-syntax-pre code .function, +[data-rad-ui-theme="light"] .docs-syntax-pre code .function-variable, +[data-rad-ui-theme="light"] .docs-syntax-pre code .class-name, +[data-rad-ui-theme="light"] .docs-syntax-pre code .tag, +[data-rad-ui-theme="light"] .docs-syntax-pre code .selector, +[data-rad-ui-theme="light"] .docs-syntax-pre code .property, +[data-rad-ui-theme="light"] .docs-syntax-pre code .maybe-class-name, +[data-rad-ui-theme="light"] .docs-syntax-pre code .builtin, +[data-rad-ui-theme="light"] .docs-syntax-pre code .constant { color: var(--rad-ui-color-blue-900); } -code .attr-name { +[data-rad-ui-theme="light"] .docs-syntax-pre code .attr-name { color: var(--rad-ui-color-red-900); } -code .attr-value { - color: var(--rad-ui-color-green-900); +[data-rad-ui-theme="light"] .docs-syntax-pre code .punctuation { + color: var(--rad-ui-text-muted); } -code .operator { +[data-rad-ui-theme="light"] .docs-syntax-pre code .operator { color: var(--rad-ui-color-purple-900); } -code .comment { +[data-rad-ui-theme="light"] .docs-syntax-pre code .comment, +[data-rad-ui-theme="light"] .docs-syntax-pre code .prolog, +[data-rad-ui-theme="light"] .docs-syntax-pre code .doctype, +[data-rad-ui-theme="light"] .docs-syntax-pre code .cdata { color: var(--rad-ui-color-gray-800); } -code .property { - color: var(--rad-ui-color-pink-900); +[data-rad-ui-theme="light"] .docs-syntax-pre code .regex, +[data-rad-ui-theme="light"] .docs-syntax-pre code .important, +[data-rad-ui-theme="light"] .docs-syntax-pre code .variable { + color: var(--rad-ui-color-blue-900); } -code .selector { - color: var(--rad-ui-color-blue-900); +[data-rad-ui-theme="light"] .docs-syntax-pre code .char { + color: var(--rad-ui-color-green-900); +} + +/* Dark: high-contrast editor palette on black panel */ +[data-rad-ui-theme="dark"] .docs-syntax-pre { + background-color: #000; + border-color: #27272a; + color: #fafafa; + box-shadow: 0 16px 48px rgb(0, 0, 0, 0.55); +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre .docs-code-block { + color: #fafafa; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .keyword { + color: #f472b6; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .string, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .attr-value, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .template-string, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .inserted { + color: #4ade80; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .number, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .boolean { + color: #2dd4bf; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .function, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .function-variable, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .class-name, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .tag, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .selector, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .property, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .maybe-class-name, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .builtin, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .constant { + color: #93c5fd; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .attr-name { + color: #f0abfc; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .punctuation, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .operator { + color: #fafafa; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .comment, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .prolog, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .doctype, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .cdata { + color: #737373; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .regex, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .important, +[data-rad-ui-theme="dark"] .docs-syntax-pre code .variable { + color: #93c5fd; +} + +[data-rad-ui-theme="dark"] .docs-syntax-pre code .char { + color: #4ade80; +} + +.docs-syntax-toolbar { + border-bottom: 1px solid var(--rad-ui-border-soft); + background: linear-gradient( + 180deg, + color-mix(in oklab, var(--rad-ui-text-primary) 3%, transparent), + transparent + ); +} + +[data-rad-ui-theme="dark"] .docs-syntax-toolbar { + border-color: #27272a; + background: #09090b; +} + +.docs-syntax-toolbar-label { + font-size: 0.68rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.14em; + color: var(--rad-ui-text-muted); +} + +[data-rad-ui-theme="dark"] .docs-syntax-toolbar-label { + color: #a1a1aa; +} + +.docs-syntax-scroll-area { + background-color: var(--rad-ui-surface-panel); +} + +[data-rad-ui-theme="dark"] .docs-syntax-scroll-area { + background-color: #000; +} + +.docs-syntax-footer { + border-top: 1px solid var(--rad-ui-border-soft); + background: color-mix(in oklab, var(--rad-ui-surface-subtle) 55%, transparent); +} + +[data-rad-ui-theme="dark"] .docs-syntax-footer { + border-color: #27272a; + background: rgb(9, 9, 11, 0.9); +} + +.docs-syntax-copy { + border-style: solid; + border-width: 1px; + border-color: var(--rad-ui-border-soft); + background: var(--rad-ui-surface-subtle); + color: var(--rad-ui-text-muted); +} + +.docs-syntax-copy:hover { + background: var(--rad-ui-surface-hover); + color: var(--rad-ui-text-primary); +} + +[data-rad-ui-theme="dark"] .docs-syntax-copy { + border-color: #3f3f46; + background: #18181b; + color: #a1a1aa; +} + +[data-rad-ui-theme="dark"] .docs-syntax-copy:hover { + background: #27272a; + color: #fafafa; +} + +.docs-syntax-expand { + border-style: solid; + border-color: var(--rad-ui-border-soft); + background: var(--rad-ui-surface-subtle); + color: var(--rad-ui-text-muted); +} + +.docs-syntax-expand:hover { + background: var(--rad-ui-surface-hover); + color: var(--rad-ui-text-primary); +} + +[data-rad-ui-theme="dark"] .docs-syntax-expand { + border-color: #3f3f46; + background: #18181b; + color: #a1a1aa; +} + +[data-rad-ui-theme="dark"] .docs-syntax-expand:hover { + background: #27272a; + color: #fafafa; } .code-block-blur { @@ -107,11 +283,22 @@ code .selector { height: 100px; width: 100%; bottom: 0; - background: linear-gradient(180deg, transparent, var(--rad-ui-surface-panel) 72%); - opacity: 0.9; + opacity: 0.95; } } +[data-rad-ui-theme="light"] .code-block-blur::before { + background: linear-gradient( + 180deg, + transparent, + var(--rad-ui-surface-panel) 72% + ); +} + +[data-rad-ui-theme="dark"] .code-block-blur::before { + background: linear-gradient(180deg, transparent, #000 72%); +} + .top-layout-line { position: relative; height: 1px; diff --git a/docs/app/layout.js b/docs/app/layout.js index 450a9ed84..e9556a53b 100644 --- a/docs/app/layout.js +++ b/docs/app/layout.js @@ -109,7 +109,7 @@ export default async function RootLayout({ children, ...props }) { const darkModeSsrValue = cookieStore.get('darkMode')?.value || false return ( - + @@ -147,7 +147,7 @@ export default async function RootLayout({ children, ...props }) { }} /> - +
    {children} diff --git a/docs/components/layout/Documentation/DocsTableOfContents.tsx b/docs/components/layout/Documentation/DocsTableOfContents.tsx index c23742987..6541b2ba4 100644 --- a/docs/components/layout/Documentation/DocsTableOfContents.tsx +++ b/docs/components/layout/Documentation/DocsTableOfContents.tsx @@ -103,7 +103,7 @@ const DocsTableOfContents = () => { return (