diff --git a/src/components/ChangelogSnippet/ChangelogCard.astro b/src/components/ChangelogSnippet/ChangelogCard.astro index 087bcbbf03a..f53563a7a4c 100644 --- a/src/components/ChangelogSnippet/ChangelogCard.astro +++ b/src/components/ChangelogSnippet/ChangelogCard.astro @@ -3,13 +3,17 @@ import { SvgTaillessArrowDownSmall, Typography } from "@chainlink/blocks" import styles from "./ChangelogCard.module.css" import type { ChangelogItem } from "./types" import { clsx } from "~/lib/clsx/clsx" +import CopyButton from "./CopyButton.tsx" interface Props { item: ChangelogItem showBorder?: boolean + autoExpand?: boolean + showCopyButton?: boolean + disableHover?: boolean } -const { item, showBorder = true } = Astro.props +const { item, showBorder = true, autoExpand = false, showCopyButton = true, disableHover = false } = Astro.props // Format the date const formatDate = (dateString: string) => { @@ -24,7 +28,12 @@ const formatDate = (dateString: string) => { const formattedDate = formatDate(item["date-of-release"]) --- -
+
@@ -51,6 +60,8 @@ const formattedDate = formatDate(item["date-of-release"]) {item["text-description"] &&
}
+ + {showCopyButton && }
@@ -114,6 +125,18 @@ const formattedDate = formatDate(item["date-of-release"]) if (!card || !content || !button) return + // Check if auto-expand is enabled + const autoExpand = (wrapper as HTMLElement).dataset.autoExpand === "true" + + if (autoExpand) { + // Auto-expand: set to full height and hide footer + ;(wrapper as HTMLElement).style.maxHeight = "none" + wrapper.classList.add("expanded") + footer.style.opacity = "0" + footer.style.pointerEvents = "none" + return // Skip the rest of the logic + } + // Wait for images to load before checking height const images = card.querySelectorAll("img") let loadedImages = 0 @@ -312,4 +335,21 @@ const formattedDate = formatDate(item["date-of-release"]) .log-item__list-chains .hidden { display: none; } + + /* Disable hover effects when data-disable-hover is true */ + [data-disable-hover="true"]:hover { + background-color: transparent !important; + } + + [data-disable-hover="true"]:hover .copyButton { + opacity: 0 !important; + } + + [data-disable-hover="true"].withBorder:hover { + background-color: transparent !important; + } + + [data-disable-hover="true"].withBorder:hover .contentFooter { + background: none !important; + } diff --git a/src/components/ChangelogSnippet/ChangelogCard.module.css b/src/components/ChangelogSnippet/ChangelogCard.module.css index 56437e6fb33..536a5c22f0e 100644 --- a/src/components/ChangelogSnippet/ChangelogCard.module.css +++ b/src/components/ChangelogSnippet/ChangelogCard.module.css @@ -9,6 +9,10 @@ background-color: var(--gray-50); } + &:hover .copyButton { + opacity: 1; + } + & .card { padding: var(--space-6x); } @@ -132,11 +136,40 @@ transition: transform 0.3s ease; } -@media screen and (max-width: 425px) { - .card { - flex-direction: column; +.copyButton { + display: flex; + height: fit-content; + gap: var(--space-2x); + border-radius: var(--space-1x); + border: 1px solid var(--border); + width: 118px; + justify-content: center; + align-items: center; + padding: var(--space-2x) 0; + transition: all 0.2s ease; + opacity: 0; + + & > svg { + color: var(--muted-foreground); + } + + &:hover { + border: 1px solid var(--foreground); } +} +.checkmark { + stroke: var(--success-foreground); +} +.copyIconMobile { + display: none; +} + +.copyIconDesktop { + display: block; +} + +@media screen and (max-width: 425px) { .changelogType { font-size: 14px; } @@ -150,6 +183,7 @@ .card { padding: var(--space-4x); gap: var(--space-4x); + flex-direction: column; } .header { @@ -172,4 +206,27 @@ .contentFooter { padding-left: var(--space-4x); } + + .copyIconDesktop { + display: none; + } + + .copyIconMobile { + display: block; + } + .copyText { + display: none; + } + + .copyButton { + width: 32px; + } +} + +@media screen and (max-width: 990px) { + .copyButton { + position: absolute; + right: var(--space-4x); + top: var(--space-4x); + } } diff --git a/src/components/ChangelogSnippet/CopyButton.tsx b/src/components/ChangelogSnippet/CopyButton.tsx new file mode 100644 index 00000000000..d52f32aa96f --- /dev/null +++ b/src/components/ChangelogSnippet/CopyButton.tsx @@ -0,0 +1,68 @@ +import { SvgCopy, Typography } from "@chainlink/blocks" +import styles from "./ChangelogCard.module.css" +import { useState } from "react" + +export default function CopyButton({ url }: { url: string }) { + const [isCopied, setIsCopied] = useState(false) + + const copyToClipboard = () => { + const mode = import.meta.env.MODE === "development" ? "http://localhost:4321" : "https://dev.chain.link" + navigator.clipboard.writeText(`${mode}/changelog/${url}`) + setIsCopied(true) + setTimeout(() => setIsCopied(false), 2000) + } + + return ( + + ) +} diff --git a/src/components/ChangelogSnippet/README.md b/src/components/ChangelogSnippet/README.md index c594abf7bf2..484eb334cd8 100644 --- a/src/components/ChangelogSnippet/README.md +++ b/src/components/ChangelogSnippet/README.md @@ -40,3 +40,54 @@ import ChangelogSnippet from "@components/ChangelogSnippet/ChangelogSnippet.astr ``` This will display the latest CCIP-related changelog entry with a link to view the full changelog. + +--- + +# ChangelogCard Component + +## What This Component Does + +The ChangelogCard component displays a single changelog item in a card format. It shows the changelog entry's type, date, title, and description with optional expand/collapse functionality. + +## How to Use It + +Import the component and pass a changelog item: + +```astro +import ChangelogCard from "@components/ChangelogSnippet/ChangelogCard.astro" import type {ChangelogItem} from "@components/ChangelogSnippet/types" +const item: ChangelogItem = { + // ... changelog item data +} + + +``` + +## Props + +| Prop | Type | Required | Default | Description | +| ---------------- | ------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------- | +| `item` | ChangelogItem | Yes | - | The changelog item to display | +| `showBorder` | boolean | No | `true` | Whether to show a border around the card | +| `autoExpand` | boolean | No | `false` | Whether to automatically expand the card to full height (skips height restrictions and hides expand/collapse button) | +| `showCopyButton` | boolean | No | `true` | Whether to show the "Copy URL" button | +| `disableHover` | boolean | No | `false` | Whether to disable hover effects (background color change) | + +## Usage Examples + +### Default Card (with border and interactions) + +```astro + +``` + +### Card without border (like on main changelog page) + +```astro + +``` + +### Fully expanded card without interactions (like on individual pages) + +```astro + +``` diff --git a/src/pages/changelog/[...id].astro b/src/pages/changelog/[...id].astro new file mode 100644 index 00000000000..f5e2b5d7eb6 --- /dev/null +++ b/src/pages/changelog/[...id].astro @@ -0,0 +1,147 @@ +--- +import BaseLayout from "~/layouts/BaseLayout.astro" +import * as CONFIG from "../../config" +import { getSecret } from "astro:env/server" +import { searchClient } from "@algolia/client-search" +import { ChangelogItem } from "~/components/ChangelogSnippet/types" +import ChangelogCard from "~/components/ChangelogSnippet/ChangelogCard.astro" +import { SvgArrowLeft2, Typography } from "@chainlink/blocks" + +export async function getStaticPaths() { + const appId = getSecret("ALGOLIA_APP_ID") + const apiKey = getSecret("PUBLIC_ALGOLIA_SEARCH_PUBLIC_API_KEY") + + // Return empty array if credentials are not available (CI/CD environments) + if (!appId || !apiKey) { + console.warn("Algolia credentials not available, skipping changelog static generation") + return [] + } + + const client = searchClient(appId, apiKey) + + try { + const records: ChangelogItem[] = [] + let currentPage = 0 + let nbPages = 1 + const hitsPerPage = 1000 // Maximum allowed by Algolia + + // Fetch all changelog items from Algolia with pagination + while (currentPage < nbPages) { + const req = await client.search({ + requests: [ + { + indexName: "Changelog", + hitsPerPage, + page: currentPage, + }, + ], + }) + + const firstResult = req.results[0] + + if ("hits" in firstResult) { + // Add hits from current page to records + const hits = firstResult.hits as ChangelogItem[] + records.push(...hits) + + // Update nbPages from the response + if ("nbPages" in firstResult) { + nbPages = firstResult.nbPages as number + } + } + + currentPage++ + } + + // Generate static paths for each changelog item + return records.map((log) => ({ + params: { id: log.slug }, + props: { log }, + })) + } catch (error) { + console.error("Error fetching changelog items:", error) + return [] + } +} + +interface Props { + log: ChangelogItem +} + +const { log } = Astro.props + +const pageTitle = `Changelog and Releases | ${CONFIG.SITE.title}` +--- + + +
+
+ + + Back to Changelog + + + Integration +
+ +
+ +
+
+
+ + diff --git a/src/pages/changelog.astro b/src/pages/changelog/index.astro similarity index 98% rename from src/pages/changelog.astro rename to src/pages/changelog/index.astro index a5301884c34..74c1207b4b4 100644 --- a/src/pages/changelog.astro +++ b/src/pages/changelog/index.astro @@ -1,6 +1,6 @@ --- import BaseLayout from "~/layouts/BaseLayout.astro" -import * as CONFIG from "../config" +import * as CONFIG from "../../config" import { Typography } from "@chainlink/blocks" import { getSecret } from "astro:env/server"