diff --git a/app/components/demo/ChangelogTimelineDemo.vue b/app/components/demo/ChangelogTimelineDemo.vue new file mode 100644 index 0000000..efaa08b --- /dev/null +++ b/app/components/demo/ChangelogTimelineDemo.vue @@ -0,0 +1,57 @@ + + + diff --git a/app/registry/blocks/changelog-timeline/ChangelogTimeline.vue b/app/registry/blocks/changelog-timeline/ChangelogTimeline.vue new file mode 100644 index 0000000..241ab37 --- /dev/null +++ b/app/registry/blocks/changelog-timeline/ChangelogTimeline.vue @@ -0,0 +1,243 @@ + + + diff --git a/app/registry/blocks/changelog-timeline/index.ts b/app/registry/blocks/changelog-timeline/index.ts new file mode 100644 index 0000000..3b4f4e6 --- /dev/null +++ b/app/registry/blocks/changelog-timeline/index.ts @@ -0,0 +1 @@ +export { default as ChangelogTimeline, type ChangelogTimelineItem, type ChangelogTimelineProps } from "./ChangelogTimeline.vue"; diff --git a/content/docs/2.components/12.changelog-timeline.md b/content/docs/2.components/12.changelog-timeline.md new file mode 100644 index 0000000..6a3450b --- /dev/null +++ b/content/docs/2.components/12.changelog-timeline.md @@ -0,0 +1,155 @@ +--- +title: ChangelogTimeline +description: Render a release timeline from app-owned changelog data with optional rich body slots. +category: content +--- + +::component-preview +--- +name: ChangelogTimelineDemo +--- +:: + +## Installation + +```bash +npx shadcn-vue@latest add "https://ui.stackhacker.io/r/changelog-timeline.json" +``` + +## Usage + +```vue + + + +``` + +## App-Owned Release Data + +`ChangelogTimeline` owns the timeline layout, latest marker, body slot boundary, and lightweight presentational states. Your app owns release fetching, repository configuration, normalization, caching, refresh lifecycle, date formatting, markdown rendering, and analytics. + +The component intentionally does not import Nuxt MDC, parse markdown, call `useFetch`, know about `ungh.cc`, or require a GitHub release response shape. Pass app-shaped items with an already formatted `dateLabel`. Use the optional raw `date` only when you want the rendered `time` element to include a machine-readable `datetime` value. + +## Rich Release Notes + +Use the `item` slot when your app wants rich release notes. The slot receives `item`, `index`, and `latest`, so the consuming app can render MDC, prose components, custom HTML, or plain Vue markup without turning that renderer into a registry dependency. + +```vue + + + +``` + +## GitHub Releases Adapter + +Keep GitHub release fetching in app code. Map your release source into `ChangelogTimelineItem` before passing it to the component. + +| Source field | Timeline item field | +|--------------|---------------------| +| `tag` | `tag` and fallback key | +| `name` or `tag` | `title` | +| `publishedAt` | raw `date` plus app-formatted `dateLabel` | +| `markdown` | `body` or slot-rendered rich content | + +This keeps the generated registry item independent from network clients, API providers, and markdown modules. + +## Examples + +### Default + +::component-preview +--- +name: ChangelogTimelineDemo +--- +:: + +### Loading + +```vue + +``` + +### Empty + +```vue + +``` + +### Error With Retry + +```vue + +``` + +## API Reference + +### Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `items` | `ChangelogTimelineItem[]` | `[]` | Release items supplied by your app. | +| `loading` | `boolean` | `false` | Shows loading skeletons when no items are present. | +| `error` | `boolean \| string` | `false` | Shows an error state. A string is rendered as supporting error copy. | +| `latestLabel` | `string` | `"Latest release"` | Label shown for the latest item. | +| `emptyTitle` | `string` | `"No releases yet"` | Empty state title. | +| `emptyDescription` | `string` | `"Published releases will appear here as changelog entries."` | Empty state description. | +| `errorTitle` | `string` | `"Could not load releases"` | Error state title. | +| `errorDescription` | `string` | `"Check the release source or try loading the changelog again."` | Error state description used when `error` is `true`. | +| `retryLabel` | `string` | `"Try again"` | Retry button label. | +| `showRetry` | `boolean` | `false` | Shows a native retry button in the error state. | +| `ariaLabel` | `string` | `"Changelog timeline"` | Accessible label for the timeline list. | +| `class` | `HTMLAttributes["class"]` | — | Additional CSS classes for the root element. | + +### Item Type + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Stable item id. Falls back to `tag` or title-derived key when omitted. | +| `tag` | `string` | Optional release tag supplied by your app. | +| `title` | `string` | Release title. | +| `dateLabel` | `string` | App-formatted display date. | +| `date` | `string` | Optional machine-readable date for the `datetime` attribute. | +| `body` | `string` | Optional plain text fallback body. | +| `latest` | `boolean` | Marks this item as latest. Defaults to the first item when no item sets `latest`. | + +### Slots + +| Slot | Props | Description | +|------|-------|-------------| +| `item` | `{ item, index, latest }` | Rich body renderer for each release item. Falls back to `item.body`. | + +### Emits + +| Event | Description | +|-------|-------------| +| `retry` | Emitted when the optional retry button is clicked. The app owns refresh behavior. | diff --git a/scripts/registry-verify.ts b/scripts/registry-verify.ts index 29340ea..5e5119f 100644 --- a/scripts/registry-verify.ts +++ b/scripts/registry-verify.ts @@ -123,6 +123,10 @@ const expectedItems: Record = { dependencies: [], registryDependencies: [], }, + "changelog-timeline": { + dependencies: [], + registryDependencies: [], + }, }; if (registryIndex) {