Skip to content

Commit 131ea07

Browse files
Feat/114 changelog (#135)
* add changelog stylin and elements * Add footer masking * Update ChangelogCard.module.css * mobile improvements * Update ChangelogCard.astro * wrap up * add prop for query * add README * add fade to show more * Update ChangelogSnippet.astro * add query types * fix merge conflicts * Update ChangelogSnippet.astro * clarify comment
1 parent d158a7e commit 131ea07

File tree

9 files changed

+752
-105
lines changed

9 files changed

+752
-105
lines changed

package-lock.json

Lines changed: 106 additions & 105 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
},
4848
"dependencies": {
4949
"@11ty/eleventy-fetch": "^4.0.1",
50+
"@algolia/client-search": "^5.41.0",
5051
"@apollo/client": "^3.14.0",
5152
"@astro-community/astro-embed-youtube": "^0.5.7",
5253
"@astrojs/mdx": "^4.3.6",
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
---
2+
import { SvgTaillessArrowDownSmall, Typography } from "@chainlink/blocks"
3+
import styles from "./ChangelogCard.module.css"
4+
import type { ChangelogItem } from "./types"
5+
6+
interface Props {
7+
item: ChangelogItem
8+
}
9+
10+
const { item } = Astro.props
11+
12+
// Format the date
13+
const formatDate = (dateString: string) => {
14+
const date = new Date(dateString)
15+
return date.toLocaleDateString("en-US", {
16+
year: "numeric",
17+
month: "short",
18+
day: "numeric",
19+
})
20+
}
21+
22+
const formattedDate = formatDate(item["date-of-release"])
23+
---
24+
25+
<div class={styles.cardWrapper} data-expandable="wrapper">
26+
<div class={styles.card} data-expandable="card">
27+
<div class={styles.header}>
28+
<div class={styles.metaSection}>
29+
<Typography variant="body-semi">
30+
{item.type}
31+
</Typography>
32+
<Typography variant="body-s" color="muted">
33+
{formattedDate}
34+
</Typography>
35+
</div>
36+
</div>
37+
38+
<div class={styles.content} data-expandable="content">
39+
<Typography
40+
variant="h6"
41+
style={{
42+
marginBottom: "var(--space-2x)",
43+
}}
44+
>
45+
{item.name}
46+
</Typography>
47+
48+
<div class={styles.descriptionContent}>
49+
{item["text-description"] && <div class="description" set:html={item["text-description"]} />}
50+
</div>
51+
</div>
52+
</div>
53+
54+
<div class={styles.contentFooter} data-expandable="footer">
55+
<button class={styles.expandButton} data-expandable="button">
56+
<span data-expandable="text">Show more</span>
57+
<SvgTaillessArrowDownSmall color="muted" />
58+
</button>
59+
</div>
60+
</div>
61+
62+
<script>
63+
function swapDataAttributeImages() {
64+
// Find all images with data-token attribute and swap src
65+
const tokenImages = document.querySelectorAll("img[data-token]")
66+
tokenImages.forEach((img) => {
67+
const imageEl = img as HTMLImageElement
68+
const tokenUrl = imageEl.getAttribute("data-token")
69+
const originalSrc = imageEl.getAttribute("src")
70+
71+
if (tokenUrl && tokenUrl !== originalSrc) {
72+
// Try to load the data-token image
73+
imageEl.setAttribute("src", tokenUrl)
74+
75+
// Fallback to original src if image fails to load
76+
imageEl.onerror = () => {
77+
imageEl.setAttribute("src", originalSrc!)
78+
imageEl.onerror = null // Remove handler to prevent infinite loop
79+
}
80+
}
81+
})
82+
83+
// Find all images with data-network attribute and swap src
84+
const networkImages = document.querySelectorAll("img[data-network]")
85+
networkImages.forEach((img) => {
86+
const imageEl = img as HTMLImageElement
87+
const networkUrl = imageEl.getAttribute("data-network")
88+
const originalSrc = imageEl.getAttribute("src")
89+
90+
if (networkUrl && networkUrl !== originalSrc) {
91+
// Try to load the data-network image
92+
imageEl.setAttribute("src", networkUrl)
93+
94+
// Fallback to original src if image fails to load
95+
imageEl.onerror = () => {
96+
imageEl.setAttribute("src", originalSrc!)
97+
imageEl.onerror = null // Remove handler to prevent infinite loop
98+
}
99+
}
100+
})
101+
}
102+
103+
function initExpandableCards() {
104+
const wrappers = document.querySelectorAll('[data-expandable="wrapper"]')
105+
106+
wrappers.forEach((wrapper) => {
107+
const card = wrapper.querySelector('[data-expandable="card"]') as HTMLElement
108+
const content = wrapper.querySelector('[data-expandable="content"]') as HTMLElement
109+
const footer = wrapper.querySelector('[data-expandable="footer"]') as HTMLElement
110+
const button = wrapper.querySelector('[data-expandable="button"]') as HTMLElement
111+
const text = button?.querySelector('[data-expandable="text"]')
112+
113+
if (!card || !content || !button) return
114+
115+
// Wait for images to load before checking height
116+
const images = card.querySelectorAll("img")
117+
let loadedImages = 0
118+
const totalImages = images.length
119+
120+
const checkCardHeight = () => {
121+
// Determine max height based on viewport
122+
const isMobile = window.innerWidth <= 768
123+
const wrapperMaxHeight = isMobile ? 300 : 400
124+
125+
// Check if card exceeds wrapper height
126+
if (card.scrollHeight <= wrapperMaxHeight) {
127+
// Card fits, hide the button and fade
128+
footer.style.opacity = "0"
129+
footer.style.pointerEvents = "none"
130+
} else {
131+
// Card exceeds wrapper, show button and fade
132+
footer.style.opacity = "1"
133+
footer.style.pointerEvents = "auto"
134+
}
135+
}
136+
137+
// Check height after each image loads
138+
if (totalImages > 0) {
139+
images.forEach((img) => {
140+
if (img.complete) {
141+
loadedImages++
142+
if (loadedImages === totalImages) {
143+
checkCardHeight()
144+
}
145+
} else {
146+
img.addEventListener("load", () => {
147+
loadedImages++
148+
if (loadedImages === totalImages) {
149+
checkCardHeight()
150+
}
151+
})
152+
img.addEventListener("error", () => {
153+
loadedImages++
154+
if (loadedImages === totalImages) {
155+
checkCardHeight()
156+
}
157+
})
158+
}
159+
})
160+
} else {
161+
// No images, check immediately
162+
checkCardHeight()
163+
}
164+
165+
let isExpanded = false
166+
167+
button.addEventListener("click", () => {
168+
isExpanded = !isExpanded
169+
170+
if (isExpanded) {
171+
// Expand wrapper to full card height
172+
;(wrapper as HTMLElement).style.maxHeight = card.scrollHeight + "px"
173+
// Hide mask and footer
174+
wrapper.classList.add("expanded")
175+
footer.style.opacity = "0"
176+
footer.style.pointerEvents = "none"
177+
if (text) text.textContent = "Show less"
178+
} else {
179+
// Collapse wrapper back
180+
;(wrapper as HTMLElement).style.maxHeight = ""
181+
// Show mask and footer
182+
wrapper.classList.remove("expanded")
183+
footer.style.opacity = "1"
184+
footer.style.pointerEvents = "auto"
185+
if (text) text.textContent = "Show more"
186+
}
187+
})
188+
})
189+
}
190+
191+
// Initialize on page load
192+
swapDataAttributeImages()
193+
initExpandableCards()
194+
195+
// Re-initialize after navigation (for SPA-like behavior)
196+
document.addEventListener("astro:page-load", () => {
197+
swapDataAttributeImages()
198+
initExpandableCards()
199+
})
200+
201+
// Re-check on resize
202+
let resizeTimer: ReturnType<typeof setTimeout>
203+
window.addEventListener("resize", () => {
204+
clearTimeout(resizeTimer)
205+
resizeTimer = setTimeout(() => {
206+
initExpandableCards()
207+
}, 250)
208+
})
209+
</script>
210+
211+
<style is:global>
212+
/* Data Entry List */
213+
.log-item__data-entry-list {
214+
grid-column-gap: 4px;
215+
grid-row-gap: 4px;
216+
flex-flow: column;
217+
display: flex;
218+
}
219+
220+
/* Data Entry Item */
221+
.log-item__data-entry {
222+
justify-content: flex-start;
223+
align-items: center;
224+
padding-top: 8px;
225+
padding-bottom: 8px;
226+
display: flex;
227+
}
228+
229+
/* Images Container */
230+
.log-item__data-entry-images {
231+
margin-right: 8px;
232+
position: relative;
233+
}
234+
235+
/* Main Image */
236+
.log-item__data-entry-img {
237+
border-radius: 100%;
238+
width: 24px;
239+
height: 24px;
240+
}
241+
242+
.log-item__data-entry-img.log-item__data-entry-img-round {
243+
border-radius: 100%;
244+
}
245+
246+
.description {
247+
> p {
248+
margin-bottom: var(--space-4x);
249+
font-size: 14px;
250+
}
251+
}
252+
253+
/* Overlay Icon */
254+
.log-item__data-entry-img-top {
255+
background-color: #fff;
256+
border: 1px solid #fff;
257+
border-radius: 4px;
258+
width: 12px;
259+
height: 12px;
260+
position: absolute;
261+
inset: auto 0% 0% auto;
262+
}
263+
264+
/* Link Text */
265+
.log-item__data-entry-info {
266+
color: #2e7bff;
267+
font-size: 14px;
268+
font-weight: 600;
269+
line-height: 20px;
270+
}
271+
272+
/* Additional Info */
273+
.log-item__data-entry-info2 {
274+
display: none;
275+
}
276+
277+
.log-item__description > p {
278+
color: var(--muted);
279+
margin-bottom: var(--space-4x);
280+
}
281+
282+
/* Network Icons List */
283+
.log-item__list-chains {
284+
display: flex;
285+
align-items: center;
286+
gap: 8px;
287+
}
288+
289+
.log-item__img-chain {
290+
width: 24px;
291+
height: 24px;
292+
border-radius: 50%;
293+
object-fit: cover;
294+
}
295+
296+
.log-item__more-chains {
297+
display: flex;
298+
align-items: center;
299+
justify-content: center;
300+
width: 24px;
301+
height: 24px;
302+
border-radius: 50%;
303+
background-color: var(--muted);
304+
font-size: 10px;
305+
font-weight: 600;
306+
color: var(--color-text-secondary);
307+
}
308+
309+
/* Hidden filter elements */
310+
.log-item__list-chains .hidden {
311+
display: none;
312+
}
313+
</style>

0 commit comments

Comments
 (0)