mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
23dd8becb9
Phase 1 — Page headers for the 9 non-mockup feature views: Skills, Gateway, Platforms, Personalities, QuickCommands, CredentialPools, Plugins, Webhooks, Profiles. Each now ships a ScarfPageHeader with title + subtitle + tokenized trailing actions (ScarfPrimary / Secondary / Ghost buttons), wrapped in .fixedSize so labels can't wrap at narrow widths. Outer .background(ScarfColor.backgroundPrimary). Phase 2 — Modal sheets: ModelPickerSheet, NousSignInSheet, RenameProjectSheet, MoveToFolderSheet, the five Templates sheets (TemplateInstall / TemplateConfig / TemplateExport / TemplateUninstall / ConfigEditorSheet), three MCPServer sheets (AddCustom / Editor / PresetPicker), AddServerSheet, ManageServersView, MissingServerView. .font(.headline) -> .scarfStyle(.headline); .buttonStyle(.borderedProminent) -> ScarfPrimaryButton(); raw text fields where touched -> ScarfTextField; cancel buttons -> ScarfGhostButton. Phase 3 — All 12 platform setup views (Discord / Email / Feishu / HomeAssistant / IMessage / Matrix / Mattermost / Signal / Slack / Telegram / Webhook / WhatsApp). Connect buttons swapped to ScarfPrimaryButton. Phase 4 — All 7 project dashboard widgets (Chart / List / Progress / Stat / Table / Text / Webview). .font(.caption) -> .scarfStyle(.caption); .background(.quaternary.opacity(0.5)) -> ScarfColor.backgroundSecondary; RoundedRectangle(cornerRadius: 8) -> ScarfRadius.lg. Phase 5 — Project sub-views: ProjectSessionsView, ProjectsSidebar, ProjectSlashCommandsView. Same token sweep. Phase 6 — Common chrome: - LoadingOverlay: .font(.callout/caption) -> .scarfStyle; secondary foreground -> ScarfColor.foregroundMuted; window-background -> ScarfColor.backgroundPrimary. - ServerSwitcherToolbar: status dot + label tokenized. - ConnectionStatusPill: status colors -> ScarfColor.success/warning/ danger; error sheet header -> ScarfPrimaryButton retry. Build green on both Mac (scarf) and iOS (scarf mobile) schemes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
3.2 KiB
Swift
76 lines
3.2 KiB
Swift
import SwiftUI
|
|
import ScarfDesign
|
|
|
|
/// Translucent loading overlay used by feature views while their VM's
|
|
/// `load()` runs in the background. Shows a centered ProgressView with
|
|
/// optional label; the underlying content stays visible (just dimmed)
|
|
/// when it's already populated, or the overlay fully covers an empty
|
|
/// section so the user sees activity instead of "nothing here yet".
|
|
///
|
|
/// Usage:
|
|
/// ```swift
|
|
/// SomeContent()
|
|
/// .loadingOverlay(viewModel.isLoading, label: "Loading credentials…", isEmpty: viewModel.pools.isEmpty)
|
|
/// ```
|
|
///
|
|
/// The `isEmpty` flag controls whether the overlay covers the full view
|
|
/// (when there's no stale content to show under it) or just dims it
|
|
/// (when refreshing existing data).
|
|
struct LoadingOverlay: ViewModifier {
|
|
let isLoading: Bool
|
|
let label: String
|
|
let isEmpty: Bool
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.overlay {
|
|
if isLoading {
|
|
if isEmpty {
|
|
// Full cover: empty state. User has no data to look at,
|
|
// so own the whole pane with the spinner.
|
|
VStack(spacing: ScarfSpace.s3) {
|
|
ProgressView()
|
|
.controlSize(.large)
|
|
Text(label)
|
|
.scarfStyle(.callout)
|
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.background(ScarfColor.backgroundPrimary)
|
|
} else {
|
|
// Stale-content refresh: top-trailing pill so the
|
|
// user sees data is being refreshed without losing
|
|
// their place.
|
|
VStack {
|
|
HStack {
|
|
Spacer()
|
|
HStack(spacing: 6) {
|
|
ProgressView()
|
|
.controlSize(.small)
|
|
Text(label)
|
|
.scarfStyle(.caption)
|
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
|
}
|
|
.padding(.horizontal, 10)
|
|
.padding(.vertical, 5)
|
|
.background(.thinMaterial, in: Capsule())
|
|
.padding(ScarfSpace.s2)
|
|
}
|
|
Spacer()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
/// Show a loading indicator while `isLoading` is true. If `isEmpty` is
|
|
/// also true, the indicator covers the full view; otherwise it shows
|
|
/// as a small refresh pill in the top-trailing corner so existing
|
|
/// content stays visible.
|
|
func loadingOverlay(_ isLoading: Bool, label: String = "Loading…", isEmpty: Bool = false) -> some View {
|
|
modifier(LoadingOverlay(isLoading: isLoading, label: label, isEmpty: isEmpty))
|
|
}
|
|
}
|