-
Notifications
You must be signed in to change notification settings - Fork 265
feat(release-notes): Group changelog entries by year with collapsible option 👌 #877
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9b1011d
db0788c
fed5194
136adbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| --- | ||
| import type { ReleaseNote } from '../release-notes' | ||
| import ReleaseNoteItem from './ReleaseNoteItem.astro' | ||
|
|
||
| interface Props { | ||
| year: string | ||
| notes: ReleaseNote[] | ||
| isOpen: boolean | ||
| twilightNote?: ReleaseNote | ||
| } | ||
|
|
||
| const { year, notes, isOpen, twilightNote } = Astro.props | ||
| --- | ||
|
|
||
| <details class="group/year year-group w-full" open={isOpen}> | ||
| <summary | ||
| class="group/summary flex cursor-pointer select-none list-none items-center justify-between gap-4 border-b border-[--zen-dark] py-8" | ||
| > | ||
| <span | ||
| class="font-bold text-dark opacity-60 transition-opacity duration-200 group-open/year:opacity-100 group-hover/summary:opacity-100" | ||
| style="font-size: clamp(2rem, 5vw, 3.5rem);" | ||
| > | ||
| {year} | ||
| </span> | ||
| <div class="flex items-center gap-3"> | ||
| { | ||
| notes.length > 0 && ( | ||
| <span class="rounded-full border border-[--zen-dark] px-3 py-1 text-xs font-medium text-dark opacity-40"> | ||
| {notes.length} release{notes.length !== 1 ? 's' : ''} | ||
| </span> | ||
| ) | ||
| } | ||
| <svg | ||
| class="chevron size-5 shrink-0 text-dark opacity-30 transition-transform duration-300 ease-in-out group-hover/summary:opacity-60" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| viewBox="0 0 20 20" | ||
| fill="currentColor" | ||
| aria-hidden="true" | ||
| > | ||
| <path | ||
| fill-rule="evenodd" | ||
| d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" | ||
| clip-rule="evenodd"></path> | ||
| </svg> | ||
| </div> | ||
| </summary> | ||
|
|
||
| <div class="year-group-content [&>*:first-child]:mt-0"> | ||
| {twilightNote && <ReleaseNoteItem {...twilightNote} isTwilight />} | ||
| {notes.map(note => <ReleaseNoteItem {...note} />)} | ||
| </div> | ||
| </details> | ||
|
|
||
| <style> | ||
| details[open] .chevron { | ||
| transform: rotate(180deg); | ||
| } | ||
|
|
||
| details[open] .year-group-content { | ||
| transition: | ||
| opacity 0.25s ease-out, | ||
| transform 0.25s ease-out; | ||
| } | ||
|
|
||
| /* | ||
| * @starting-style defines the initial state an element transitions FROM | ||
| * when it first becomes visible (e.g. display: none → block). | ||
| * Without it, CSS transitions are ignored on first paint since there is no prior state. | ||
| * Learn more: | ||
| * MDN — https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@starting-style | ||
| * Video — https://www.youtube.com/watch?v=vmDEHAzj2XE | ||
| */ | ||
| @starting-style { | ||
| details[open] .year-group-content { | ||
| opacity: 0; | ||
| transform: translateY(-6px); | ||
| } | ||
| } | ||
| </style> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
| import { Modal, ModalBody, ModalHeader } from 'free-astro-components' | ||
| import Button from '~/components/Button.astro' | ||
| import Description from '~/components/Description.astro' | ||
| import ReleaseNoteItem from '~/components/ReleaseNoteItem.astro' | ||
| import ReleaseNoteYearGroup from '~/components/ReleaseNoteYearGroup.astro' | ||
| import ArrowUpIcon from '~/icons/ArrowUp.astro' | ||
| import Layout from '~/layouts/Layout.astro' | ||
| import { releaseNotes as releaseNotesData, releaseNotesTwilight } from '~/release-notes' | ||
|
|
@@ -15,13 +15,31 @@ const { | |
| routes: { releaseNotes }, | ||
| layout, | ||
| } = getUI(locale) | ||
|
|
||
| const currentYear = new Date().getFullYear().toString() | ||
|
|
||
| const releasesByYear = releaseNotesData.reduce( | ||
| (groups, note) => { | ||
| if (!note.date) return groups | ||
| const year = note.date.split('/')[2] | ||
| if (!groups[year]) groups[year] = [] | ||
| groups[year].push(note) | ||
| return groups | ||
| }, | ||
| {} as Record<string, typeof releaseNotesData> | ||
| ) | ||
|
|
||
| const sortedYears = Object.keys(releasesByYear).sort((a, b) => Number(b) - Number(a)) | ||
| --- | ||
|
|
||
| <Layout title={layout.releaseNotes.title}> | ||
| <main | ||
| class="container flex h-full min-h-[1000px] flex-1 flex-col items-center justify-center py-4" | ||
| > | ||
| <div id="release-notes" class="py-42 flex min-h-screen w-full flex-col justify-center gap-8"> | ||
| <div | ||
| id="release-notes" | ||
| class="py-42 flex min-h-screen w-full flex-col justify-center gap-8 pb-24" | ||
| > | ||
| <Description class="mt-48 text-6xl font-bold">{releaseNotes.topSection.title}</Description> | ||
| <p | ||
| class="text-base opacity-55" | ||
|
|
@@ -39,11 +57,20 @@ const { | |
| </Button> | ||
| </div> | ||
| { | ||
| releaseNotesTwilight.features.length || releaseNotesTwilight.fixes.length ? ( | ||
| <ReleaseNoteItem {...releaseNotesTwilight} isTwilight /> | ||
| ) : null | ||
| sortedYears.map(year => ( | ||
| <ReleaseNoteYearGroup | ||
| year={year} | ||
| notes={releasesByYear[year]} | ||
| isOpen={year === currentYear} | ||
| twilightNote={ | ||
| year === currentYear && | ||
| (releaseNotesTwilight.features.length || releaseNotesTwilight.fixes.length) | ||
| ? releaseNotesTwilight | ||
| : undefined | ||
| } | ||
| /> | ||
| )) | ||
| } | ||
| {releaseNotesData.map(notes => <ReleaseNoteItem {...notes} />)} | ||
| </div> | ||
| </main> | ||
| <Button id="scroll-top" isPrimary class="fixed bottom-8 right-8" onclick="window.scrollTo(0, 0)"> | ||
|
|
@@ -104,7 +131,14 @@ const { | |
| if (!version) return | ||
| window.location.hash = version | ||
|
|
||
| const versionDetails = document.getElementById(version)?.getElementsByTagName('details') | ||
| const versionEl = document.getElementById(version) | ||
|
|
||
| // Ensure the target version's year group is expanded before scrolling, | ||
| // in case the user picks a version from a collapsed past year. | ||
| const parentYearGroup = versionEl?.closest('details.year-group') as HTMLDetailsElement | null | ||
| if (parentYearGroup) parentYearGroup.open = true | ||
|
|
||
|
Comment on lines
+134
to
+140
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this supposed to do btw?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use case here ... when someone is choosing and older version from the "Navigate to version" button, we have to make sure that on the page we're expanding a group that might be collapsed. For example:
I hope this makes sense 😅 |
||
| const versionDetails = versionEl?.getElementsByTagName('details') | ||
| if (versionDetails && versionDetails.length > 0) { | ||
| Array.from(versionDetails).forEach(accordion => { | ||
| accordion.setAttribute('open', '') | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't need this comment