Components PulseTimeline
PulseTimeline
History with a clear visual rhythm.
interactive demo
✓PublishedNowThe release is live.
◌Reviewed2hDesign QA approved.
+CreatedYesterdayWorkspace initialized.
A live browser stand-in using the exact tokens, motion and API of the SwiftUI component.
Want all components at once?
Open the studio
History with a clear visual rhythm.
A compact activity and release timeline with semantic event content and restrained motion.
Activity feeds
Changelog-ready
Semantic event labels
Reduce Motion aware
Usage
PulseTimeline(events) Install
$ pulse add timeline Source files
Sources/PulseUI/Components/PulseTimeline/PulseTimeline.swift
Full source Sources/PulseUI/Components/PulseTimeline/PulseTimeline.swift
import SwiftUI
// MARK: - PulseTimeline
public struct PulseTimelineEvent: Identifiable, Sendable {
public let id: String
public let title: String
public let detail: String?
public let date: String
public let systemImage: String
public init(
id: String,
title: String,
detail: String? = nil,
date: String,
systemImage: String = "circle.fill"
) {
self.id = id
self.title = title
self.detail = detail
self.date = date
self.systemImage = systemImage
}
}
public struct PulseTimeline: View {
@Environment(\.pulseTheme) private var theme
@Environment(\.accessibilityReduceMotion) private var reduceMotion
private let events: [PulseTimelineEvent]
public init(_ events: [PulseTimelineEvent]) {
self.events = events
}
public var body: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(Array(events.enumerated()), id: \.element.id) { index, event in
HStack(alignment: .top, spacing: theme.spacing.md) {
VStack(spacing: 0) {
Image(systemName: event.systemImage)
.font(.caption.weight(.bold))
.foregroundStyle(theme.colors.accent)
.frame(width: 24, height: 24)
.background(theme.colors.accent.opacity(0.14), in: Circle())
if index < events.count - 1 {
Rectangle()
.fill(theme.colors.border)
.frame(width: 1)
.frame(maxHeight: .infinity)
}
}
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .firstTextBaseline) {
Text(event.title)
.font(theme.typography.headline.font)
.foregroundStyle(theme.colors.foreground)
Spacer()
Text(event.date)
.font(theme.typography.caption.font)
.foregroundStyle(theme.colors.foregroundTertiary)
}
if let detail = event.detail {
Text(detail)
.font(theme.typography.body.font)
.foregroundStyle(theme.colors.foregroundSecondary)
}
}
.padding(.bottom, theme.spacing.lg)
}
}
}
.animation(reduceMotion ? .none : theme.motion.normal, value: events.map(\.id))
.accessibilityElement(children: .contain)
.accessibilityLabel("Timeline")
}
}