Appearance
Gantt content, media and context actions
Gantt guide · Shared foundations
All APIs below belong to ChronoSketchUI and execute on the main actor. They are value-style modifiers on TimelineView; retain your TimelineModel as before. They do not add presentation fields to Core entries, resources or assignments. Existing TimelineView initializers and default text rendering remain compatible.
Standard media and colors
swift
TimelineView(model: timeline, today: today)
.resourceMedia { resource in
// Resolve consumer-owned metadata using a stable resource ID.
TimelineMedia(initials: String(resource.label.prefix(2)))
}
.groupIcon { group in
group.key == "site" ? "mappin.and.ellipse" : "building.2"
}
.entryAppearance { context in
TimelineEntryAppearance(
tint: context.assignment.resourceId == "room" ? .teal : .purple,
media: TimelineMedia(
source: .asset("GuestAvatar"), systemImage: "person.fill"
)
)
}TimelineMedia.Source supports .asset(name) in the app's main asset catalog and .remote(URL). Remote images use SwiftUI AsyncImage. Loading/failure shows systemImage, otherwise initials (first two characters), otherwise an empty reserved media area. Supply valid SF Symbol and asset names; missing asset names are not remote loading failures and must be corrected by the consumer.
The default .avatar variant fills and clips a circle; .logo contains the whole image within a subtly rounded square. Resource media is 24 points; entry media is 20 points. Space stays reserved while loading so row/lane geometry remains stable. Images are decorative and hidden from accessibility. Keep meaningful names in TimelineResource.label and TimelineEntry.title.
The consumer owns URLs, authorization, asset provenance and any specialized cache. For authenticated requests, another asset bundle, or a custom image pipeline, use the content builders below. The library adds no image service or persistence layer. TimelineEntryAppearance.tint colors the bar background/border. Its default nil preserves the ambient SwiftUI .tint(...); custom content may choose its own foreground style. Group icon resolution receives the grouping key, original group value/path/count/expanded state and localized default title.
SwiftUI content builders
swift
TimelineView(model: timeline, today: today)
.entryContent { context in
if context.isAccessibilityList {
VStack(alignment: .leading) {
TimelineDefaultEntryContent(context: context)
Text("VIP").font(.caption2.bold())
}
} else {
TimelineDefaultEntryContent(context: context)
}
}
.resourceContent { context in
TimelineDefaultResourceContent(
context: context, media: TimelineMedia(systemImage: "house")
)
}
.groupContent { context in
Text(context.title).italic()
}These are the native equivalent of presentation slots. Each builder replaces only its content. The Timeline retains sizing, clipping, lane placement, disclosure, resource counts, selection, gestures and accessible labels/actions. Custom content is decorative: do not place Buttons, Menus or other interactive controls inside it. Use selection or context actions for interaction. Keep the builders side-effect-free.
TimelineEntryContentContext provides original entry, assignment, resource, transient preview, availableWidth and isAccessibilityList. Preview contains proposed dates during a drag; original data and selection callbacks remain exact, even when bars are clipped. Canvas bars stay 28 points high. Width is the outer bar width, including its horizontal content padding; account for this in custom layouts. At accessibility text sizes the entry appears in a list with unconstrained width (.infinity) and no fixed content height. Use isAccessibilityList to adapt.
Resource contexts provide resource and isAccessibilityList. Resource labels retain the 132-point sidebar width and component-computed row height in the canvas; custom content cannot increase it. Group content replaces the title only; disclosure and resource count remain outside the slot. Group rows stay 44 points high in the canvas. Standard controls and labels also remain available in the accessibility list.
TimelineDefaultEntryContent, TimelineDefaultResourceContent and TimelineMediaView(media:size:) are public building blocks. When composing a default inside your own builder, pass the desired appearance/media explicitly. A custom entry builder replaces default media/title content, but the entry appearance's bar tint still applies. The builder receives current inputs on SwiftUI updates; keep custom business metadata in consumer state, indexed by stable IDs.
Consumer context actions
swift
TimelineView(model: timeline, today: today)
.entryActions { context in
[
TimelineEntryAction(id: "details", title: "Details", systemImage: "info.circle"),
TimelineEntryAction(
id: "delete", title: "Delete", systemImage: "trash",
isEnabled: canDelete(context.entry.id), isDestructive: true
)
]
} onAction: { actionID, context in
// Present your own details, confirmation or persistence flow.
handleAction(actionID, context.entry, context.assignment)
}Titles are consumer-localized. Use nonblank unique IDs and nonblank titles. Invalid items are omitted; the first valid item wins for duplicate IDs. Disabled actions remain visible but cannot dispatch. Return [] to omit the menu. The resolver must be synchronous and side-effect-free; availability is rechecked at dispatch.
In the canvas, long press opens an anchored action popover without lifting or relocating the bar. There is no prior activation mode, movement or resize grip. Tap still selects the original entry; accessible actions and the large-text list's menu remain available. Activation uses the shared soft haptic feedback.
The accessibility list always exposes the menu below each applicable entry. VoiceOver also receives named actions. Tapping the large-text list menu uses shared soft feedback respecting hapticsEnabled; the anchored popover uses the same activation feedback. Action success/failure feedback belongs to the consumer after confirmation, as with interval editing. There is no additional automatic persistence or success pulse.
Data/navigation/grouping/scale revisions reject stale callbacks. Before dispatch, the component checks the original entry/assignment against current data and resolves the action's current enabled state. Removed or changed entries cannot trigger a stale action. Primary selection is separate and always receives the original entry/assignment. This native menu interaction is not a Web right-click/keyboard-shortcut contract.
Demo
The palette menu beside grouping selects Default, Icons and avatars, or Custom content. It also enables/disables actions and interval editing independently. The default media example includes local resource/portrait images, initials, symbols and a deliberately unavailable image with a symbol fallback. Custom content adds a confirmation badge, resource location and styled group titles. Details is a working context action; Delete is deliberately disabled to demonstrate permissions. Demo photographs reuse the existing owner-provided Web fixtures with the same provenance; they are not part of the Swift package runtime.
Titles and canvas configuration
Use .title("Bookings") and .resourceTitle("Rooms") to override the component and sidebar headings; nil restores native defaults. .showZoom(false) hides zoom controls without changing the selected zoom. .gridSize(height: 600, resourceWidth: 180) configures the inner viewport/sidebar in points; finite values are clamped to at least 100/44 respectively, non-finite values restore 540/132. Outer layout still uses SwiftUI frame modifiers. Configuration is value-based and retains model state, grouping and confirmed data.