import AppKitimport CodexBarCoreimport SwiftUI enum UsageMenuCardLayout { static let horizontalPadding: CGFloat = 20 static let headerOnlyVerticalPadding: CGFloat = 7 static let sectionTopPadding: CGFloat = 6 static let sectionBottomPadding: CGFloat = 6 static let headerLineSpacing: CGFloat = 4 static let headerColumnSpacing: CGFloat = 12} /// SwiftUI card used inside the NSMenu to mirror Apple's rich menu panels.struct UsageMenuCardView: View { struct Model { enum PercentStyle: String { case left case used var labelSuffix: String { switch self { case .left: L("usage_percent_suffix_left") case .used: L("usage_percent_suffix_used") } } var accessibilityLabel: String { switch self { case .left: L("Usage remaining") case .used: L("Usage used") } } } struct Metric: Identifiable { let id: String let title: String let percent: Double let percentStyle: PercentStyle let statusText: String? let resetText: String? let detailText: String? let detailLeftText: String? let detailRightText: String? let pacePercent: Double? let paceOnTop: Bool let warningMarkerPercents: [Double] let cardStyle: Bool init( id: String, title: String, percent: Double, percentStyle: PercentStyle, statusText: String? = nil, resetText: String?, detailText: String?, detailLeftText: String?, detailRightText: String?, pacePercent: Double?, paceOnTop: Bool, warningMarkerPercents: [Double] = [], cardStyle: Bool = false) { self.id = id self.title = title self.percent = percent self.percentStyle = percentStyle self.statusText = statusText self.resetText = resetText self.detailText = detailText self.detailLeftText = detailLeftText self.detailRightText = detailRightText self.pacePercent = pacePercent self.paceOnTop = paceOnTop self.warningMarkerPercents = warningMarkerPercents self.cardStyle = cardStyle } var percentLabel: String { String(format: "%.0f%% %@", self.percent, self.percentStyle.labelSuffix) } } enum SubtitleStyle { case info case loading case error } struct TokenUsageSection { let sessionLine: String let monthLine: String let hintLine: String? let errorLine: String? let errorCopyText: String? } struct ProviderCostSection { let title: String let percentUsed: Double? let spendLine: String let percentLine: String? } let provider: UsageProvider let providerName: String let email: String let subtitleText: String let subtitleStyle: SubtitleStyle var usesLiveSubtitle: Bool = false let planText: String? let metrics: [Metric] let usageNotes: [String] let openAIAPIUsage: OpenAIAPIUsageSnapshot? let inlineUsageDashboard: InlineUsageDashboardModel? let creditsText: String? let creditsRemaining: Double? let creditsHintText: String? let creditsHintCopyText: String? let providerCost: ProviderCostSection? let tokenUsage: TokenUsageSection? let placeholder: String? let progressColor: Color } let model: Model let width: CGFloat @Environment(\.menuItemHighlighted) private var isHighlighted @Environment(\.menuCardRefreshMonitor) private var refreshMonitor static func popupMetricTitle(provider: UsageProvider, metric: Model.Metric) -> String { if provider == .openrouter, metric.id == "primary" { return L("API key limit") } return metric.title } var body: some View { let liveModel = self.liveModel VStack(alignment: .leading, spacing: 6) { UsageMenuCardHeaderView(model: self.model) if self.hasDetails(model: liveModel) { Divider() } if !liveModel.usesStackedDetailLayout { if let dashboard = liveModel.inlineUsageDashboard { InlineUsageDashboardContent(model: dashboard) } else if !liveModel.usageNotes.isEmpty { UsageNotesContent(notes: liveModel.usageNotes) } else if let placeholder = liveModel.placeholder { Text(placeholder) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .font(.subheadline) } } else { let hasUsage = liveModel.hasUsageContent let hasCredits = liveModel.creditsText != nil let hasProviderCost = liveModel.providerCost != nil let hasCost = liveModel.tokenUsage != nil || hasProviderCost VStack(alignment: .leading, spacing: 12) { if hasUsage { VStack(alignment: .leading, spacing: 12) { ForEach(liveModel.metrics, id: \.id) { metric in MetricRow( metric: metric, title: Self.popupMetricTitle(provider: liveModel.provider, metric: metric), progressColor: liveModel.progressColor) } if let dashboard = liveModel.inlineUsageDashboard { InlineUsageDashboardContent(model: dashboard) } else if !liveModel.usageNotes.isEmpty { UsageNotesContent(notes: liveModel.usageNotes) } else if let placeholder = liveModel.placeholder { Text(placeholder) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .font(.subheadline) } } } if hasUsage, hasCredits || hasCost { Divider() } if let credits = liveModel.creditsText { CreditsBarContent( creditsText: credits, creditsRemaining: liveModel.creditsRemaining, hintText: liveModel.creditsHintText, hintCopyText: liveModel.creditsHintCopyText, progressColor: liveModel.progressColor) } if hasCredits, hasCost { Divider() } if let providerCost = liveModel.providerCost { ProviderCostContent( section: providerCost, progressColor: liveModel.progressColor) } if hasProviderCost, liveModel.tokenUsage != nil { Divider() } if let tokenUsage = liveModel.tokenUsage { VStack(alignment: .leading, spacing: 6) { Text(L("cost_header_estimated")) .font(.body) .fontWeight(.medium) Text(tokenUsage.sessionLine) .font(.footnote) .lineLimit(1) Text(tokenUsage.monthLine) .font(.footnote) .lineLimit(1) if let hint = tokenUsage.hintLine, !hint.isEmpty { Text(hint) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) } if let error = tokenUsage.errorLine, !error.isEmpty { Text(error) .font(.footnote) .foregroundStyle(MenuHighlightStyle.error(self.isHighlighted)) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) .overlay { ClickToCopyOverlay(copyText: tokenUsage.errorCopyText ?? error) } } } } } .padding(.bottom, liveModel.creditsText == nil ? 6 : 0) } } .padding(.horizontal, UsageMenuCardLayout.horizontalPadding) .padding( .top, self.hasDetails(model: liveModel) ? UsageMenuCardLayout.sectionTopPadding : UsageMenuCardLayout.headerOnlyVerticalPadding) .padding( .bottom, self.hasDetails(model: liveModel) ? UsageMenuCardLayout.sectionBottomPadding : UsageMenuCardLayout.headerOnlyVerticalPadding) .frame(width: self.width, alignment: .leading) } private var liveModel: Model { guard self.model.usesLiveSubtitle else { return self.model } return self.refreshMonitor?.model(for: self.model.provider, fallback: self.model) ?? self.model } private func hasDetails(model: Model) -> Bool { model.hasUsageContent || model.usesStackedDetailLayout }} private struct UsageMenuCardHeaderView: View { let model: UsageMenuCardView.Model @Environment(\.menuItemHighlighted) private var isHighlighted @Environment(\.menuCardRefreshMonitor) private var refreshMonitor var body: some View { VStack(alignment: .leading, spacing: UsageMenuCardLayout.headerLineSpacing) { HStack(alignment: .firstTextBaseline, spacing: UsageMenuCardLayout.headerColumnSpacing) { Text(self.model.providerName).font(.headline) .fontWeight(.semibold) .lineLimit(1).truncationMode(.tail).layoutPriority(1) Spacer() Text(self.model.email).font(.subheadline) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1).truncationMode(.middle) } let liveSubtitle = self.liveSubtitle // Keep the geometry AppKit measured for this hosted row. A new error stays one line // until the next rebuild; a recovered error keeps its reserved height until then. let usesErrorLayout = self.model.subtitleStyle == .error let subtitleAlignment: VerticalAlignment = usesErrorLayout ? .top : .firstTextBaseline HStack(alignment: subtitleAlignment, spacing: UsageMenuCardLayout.headerColumnSpacing) { if usesErrorLayout { Text(self.model.subtitleText) .font(.footnote) .lineLimit(4) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .padding(.bottom, 4) .hidden() .overlay(alignment: .topLeading) { Text(liveSubtitle.text) .font(.footnote) .foregroundStyle(self.subtitleColor(for: liveSubtitle.style)) .lineLimit(4) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) } .clipped() .layoutPriority(1) } else { Text(liveSubtitle.text) .font(.footnote) .foregroundStyle(self.subtitleColor(for: liveSubtitle.style)) .lineLimit(1) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .layoutPriority(1) } Spacer() if usesErrorLayout { let showsCopyButton = liveSubtitle.style == .error && !liveSubtitle.text.isEmpty CopyIconButton(copyText: liveSubtitle.text, isHighlighted: self.isHighlighted) .opacity(showsCopyButton ? 1 : 0) .allowsHitTesting(showsCopyButton) .accessibilityHidden(!showsCopyButton) } if let plan = self.model.planText { Text(plan) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } private var liveSubtitle: MenuCardLiveSubtitle { let fallback = MenuCardLiveSubtitle(text: self.model.subtitleText, style: self.model.subtitleStyle) guard self.model.usesLiveSubtitle else { return fallback } return self.refreshMonitor?.subtitle(for: self.model.provider, fallback: fallback) ?? fallback } private func subtitleColor(for style: UsageMenuCardView.Model.SubtitleStyle) -> Color { switch style { case .info: MenuHighlightStyle.secondary(self.isHighlighted) case .loading: MenuHighlightStyle.secondary(self.isHighlighted) case .error: MenuHighlightStyle.error(self.isHighlighted) } }} private struct CopyIconButtonStyle: ButtonStyle { let isHighlighted: Bool func makeBody(configuration: Configuration) -> some View { configuration.label .padding(4) .background { RoundedRectangle(cornerRadius: 4, style: .continuous) .fill(MenuHighlightStyle.secondary(self.isHighlighted).opacity(configuration.isPressed ? 0.18 : 0)) } .scaleEffect(configuration.isPressed ? 0.94 : 1) .animation(.easeOut(duration: 0.12), value: configuration.isPressed) }} private struct CopyIconButton: View { let copyText: String let isHighlighted: Bool @State private var didCopy = false @State private var resetTask: Task<Void, Never>? var body: some View { Button { self.handleCopy() } label: { Image(systemName: self.didCopy ? "checkmark" : "doc.on.doc") .font(.caption2.weight(.semibold)) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .frame(width: 18, height: 18) } .buttonStyle(CopyIconButtonStyle(isHighlighted: self.isHighlighted)) .accessibilityLabel(self.didCopy ? L("Copied") : L("Copy error")) } private func handleCopy() { let text = self.copyText self.resetTask?.cancel() MenuPasteboardCopy.perform(text, completion: { self.didCopy = true self.resetTask = Task { @MainActor in try? await Task.sleep(for: .seconds(0.9)) self.didCopy = false } }) }} private struct ProviderCostContent: View { let section: UsageMenuCardView.Model.ProviderCostSection let progressColor: Color @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 6) { Text(self.section.title) .font(.body) .fontWeight(.medium) if let percentUsed = self.section.percentUsed { UsageProgressBar( percent: percentUsed, tint: self.progressColor, accessibilityLabel: L("Extra usage spent")) } HStack(alignment: .firstTextBaseline) { Text(self.section.spendLine).font(.footnote).lineLimit(1) Spacer() if let percentLine = self.section.percentLine { Text(percentLine) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } }} private struct MetricRow: View { let metric: UsageMenuCardView.Model.Metric let title: String let progressColor: Color @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 6) { Text(self.title) .font(.body) .fontWeight(.medium) if let statusText = self.metric.statusText { Text(statusText) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } else { UsageProgressBar( percent: self.metric.percent, tint: self.progressColor, accessibilityLabel: self.metric.percentStyle.accessibilityLabel, pacePercent: self.metric.pacePercent, paceOnTop: self.metric.paceOnTop, warningMarkerPercents: self.metric.warningMarkerPercents) VStack(alignment: .leading, spacing: 2) { HStack(alignment: .firstTextBaseline) { Text(self.metric.percentLabel) .font(.footnote) .lineLimit(1) Spacer() if let rightLabel = self.metric.resetText { Text(rightLabel) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } if self.metric.detailLeftText != nil || self.metric.detailRightText != nil { HStack(alignment: .firstTextBaseline) { if let detailLeft = self.metric.detailLeftText { Text(detailLeft) .font(.footnote) .foregroundStyle(MenuHighlightStyle.primary(self.isHighlighted)) .lineLimit(1) } Spacer() if let detailRight = self.metric.detailRightText { Text(detailRight) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } .frame(maxWidth: .infinity, alignment: .leading) if let detail = self.metric.detailText { Text(detail) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(self.metric.cardStyle ? 10 : 0) .background(self.metric.cardStyle ? Color.secondary.opacity(self.isHighlighted ? 0.2 : 0.08) : Color.clear) .clipShape(RoundedRectangle(cornerRadius: self.metric.cardStyle ? 10 : 0)) }} private struct UsageNotesContent: View { let notes: [String] @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View {Lines 1–500 of 1603. Load more
import AppKit import CodexBarCore import SwiftUI enum UsageMenuCardLayout { static let horizontalPadding: CGFloat = 20 static let headerOnlyVerticalPadding: CGFloat = 7 static let sectionTopPadding: CGFloat = 6 static let sectionBottomPadding: CGFloat = 6 static let headerLineSpacing: CGFloat = 4 static let headerColumnSpacing: CGFloat = 12 } /// SwiftUI card used inside the NSMenu to mirror Apple's rich menu panels. struct UsageMenuCardView: View { struct Model { enum PercentStyle: String { case left case used var labelSuffix: String { switch self { case .left: L("usage_percent_suffix_left") case .used: L("usage_percent_suffix_used") } } var accessibilityLabel: String { switch self { case .left: L("Usage remaining") case .used: L("Usage used") } } } struct Metric: Identifiable { let id: String let title: String let percent: Double let percentStyle: PercentStyle let statusText: String? let resetText: String? let detailText: String? let detailLeftText: String? let detailRightText: String? let pacePercent: Double? let paceOnTop: Bool let warningMarkerPercents: [Double] let cardStyle: Bool init( id: String, title: String, percent: Double, percentStyle: PercentStyle, statusText: String? = nil, resetText: String?, detailText: String?, detailLeftText: String?, detailRightText: String?, pacePercent: Double?, paceOnTop: Bool, warningMarkerPercents: [Double] = [], cardStyle: Bool = false) { self.id = id self.title = title self.percent = percent self.percentStyle = percentStyle self.statusText = statusText self.resetText = resetText self.detailText = detailText self.detailLeftText = detailLeftText self.detailRightText = detailRightText self.pacePercent = pacePercent self.paceOnTop = paceOnTop self.warningMarkerPercents = warningMarkerPercents self.cardStyle = cardStyle } var percentLabel: String { String(format: "%.0f%% %@", self.percent, self.percentStyle.labelSuffix) } } enum SubtitleStyle { case info case loading case error } struct TokenUsageSection { let sessionLine: String let monthLine: String let hintLine: String? let errorLine: String? let errorCopyText: String? } struct ProviderCostSection { let title: String let percentUsed: Double? let spendLine: String let percentLine: String? } let provider: UsageProvider let providerName: String let email: String let subtitleText: String let subtitleStyle: SubtitleStyle var usesLiveSubtitle: Bool = false let planText: String? let metrics: [Metric] let usageNotes: [String] let openAIAPIUsage: OpenAIAPIUsageSnapshot? let inlineUsageDashboard: InlineUsageDashboardModel? let creditsText: String? let creditsRemaining: Double? let creditsHintText: String? let creditsHintCopyText: String? let providerCost: ProviderCostSection? let tokenUsage: TokenUsageSection? let placeholder: String? let progressColor: Color } let model: Model let width: CGFloat @Environment(\.menuItemHighlighted) private var isHighlighted @Environment(\.menuCardRefreshMonitor) private var refreshMonitor static func popupMetricTitle(provider: UsageProvider, metric: Model.Metric) -> String { if provider == .openrouter, metric.id == "primary" { return L("API key limit") } return metric.title } var body: some View { let liveModel = self.liveModel VStack(alignment: .leading, spacing: 6) { UsageMenuCardHeaderView(model: self.model) if self.hasDetails(model: liveModel) { Divider() } if !liveModel.usesStackedDetailLayout { if let dashboard = liveModel.inlineUsageDashboard { InlineUsageDashboardContent(model: dashboard) } else if !liveModel.usageNotes.isEmpty { UsageNotesContent(notes: liveModel.usageNotes) } else if let placeholder = liveModel.placeholder { Text(placeholder) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .font(.subheadline) } } else { let hasUsage = liveModel.hasUsageContent let hasCredits = liveModel.creditsText != nil let hasProviderCost = liveModel.providerCost != nil let hasCost = liveModel.tokenUsage != nil || hasProviderCost VStack(alignment: .leading, spacing: 12) { if hasUsage { VStack(alignment: .leading, spacing: 12) { ForEach(liveModel.metrics, id: \.id) { metric in MetricRow( metric: metric, title: Self.popupMetricTitle(provider: liveModel.provider, metric: metric), progressColor: liveModel.progressColor) } if let dashboard = liveModel.inlineUsageDashboard { InlineUsageDashboardContent(model: dashboard) } else if !liveModel.usageNotes.isEmpty { UsageNotesContent(notes: liveModel.usageNotes) } else if let placeholder = liveModel.placeholder { Text(placeholder) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .font(.subheadline) } } } if hasUsage, hasCredits || hasCost { Divider() } if let credits = liveModel.creditsText { CreditsBarContent( creditsText: credits, creditsRemaining: liveModel.creditsRemaining, hintText: liveModel.creditsHintText, hintCopyText: liveModel.creditsHintCopyText, progressColor: liveModel.progressColor) } if hasCredits, hasCost { Divider() } if let providerCost = liveModel.providerCost { ProviderCostContent( section: providerCost, progressColor: liveModel.progressColor) } if hasProviderCost, liveModel.tokenUsage != nil { Divider() } if let tokenUsage = liveModel.tokenUsage { VStack(alignment: .leading, spacing: 6) { Text(L("cost_header_estimated")) .font(.body) .fontWeight(.medium) Text(tokenUsage.sessionLine) .font(.footnote) .lineLimit(1) Text(tokenUsage.monthLine) .font(.footnote) .lineLimit(1) if let hint = tokenUsage.hintLine, !hint.isEmpty { Text(hint) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) } if let error = tokenUsage.errorLine, !error.isEmpty { Text(error) .font(.footnote) .foregroundStyle(MenuHighlightStyle.error(self.isHighlighted)) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) .overlay { ClickToCopyOverlay(copyText: tokenUsage.errorCopyText ?? error) } } } } } .padding(.bottom, liveModel.creditsText == nil ? 6 : 0) } } .padding(.horizontal, UsageMenuCardLayout.horizontalPadding) .padding( .top, self.hasDetails(model: liveModel) ? UsageMenuCardLayout.sectionTopPadding : UsageMenuCardLayout.headerOnlyVerticalPadding) .padding( .bottom, self.hasDetails(model: liveModel) ? UsageMenuCardLayout.sectionBottomPadding : UsageMenuCardLayout.headerOnlyVerticalPadding) .frame(width: self.width, alignment: .leading) } private var liveModel: Model { guard self.model.usesLiveSubtitle else { return self.model } return self.refreshMonitor?.model(for: self.model.provider, fallback: self.model) ?? self.model } private func hasDetails(model: Model) -> Bool { model.hasUsageContent || model.usesStackedDetailLayout } } private struct UsageMenuCardHeaderView: View { let model: UsageMenuCardView.Model @Environment(\.menuItemHighlighted) private var isHighlighted @Environment(\.menuCardRefreshMonitor) private var refreshMonitor var body: some View { VStack(alignment: .leading, spacing: UsageMenuCardLayout.headerLineSpacing) { HStack(alignment: .firstTextBaseline, spacing: UsageMenuCardLayout.headerColumnSpacing) { Text(self.model.providerName).font(.headline) .fontWeight(.semibold) .lineLimit(1).truncationMode(.tail).layoutPriority(1) Spacer() Text(self.model.email).font(.subheadline) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1).truncationMode(.middle) } let liveSubtitle = self.liveSubtitle // Keep the geometry AppKit measured for this hosted row. A new error stays one line // until the next rebuild; a recovered error keeps its reserved height until then. let usesErrorLayout = self.model.subtitleStyle == .error let subtitleAlignment: VerticalAlignment = usesErrorLayout ? .top : .firstTextBaseline HStack(alignment: subtitleAlignment, spacing: UsageMenuCardLayout.headerColumnSpacing) { if usesErrorLayout { Text(self.model.subtitleText) .font(.footnote) .lineLimit(4) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .padding(.bottom, 4) .hidden() .overlay(alignment: .topLeading) { Text(liveSubtitle.text) .font(.footnote) .foregroundStyle(self.subtitleColor(for: liveSubtitle.style)) .lineLimit(4) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) } .clipped() .layoutPriority(1) } else { Text(liveSubtitle.text) .font(.footnote) .foregroundStyle(self.subtitleColor(for: liveSubtitle.style)) .lineLimit(1) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .layoutPriority(1) } Spacer() if usesErrorLayout { let showsCopyButton = liveSubtitle.style == .error && !liveSubtitle.text.isEmpty CopyIconButton(copyText: liveSubtitle.text, isHighlighted: self.isHighlighted) .opacity(showsCopyButton ? 1 : 0) .allowsHitTesting(showsCopyButton) .accessibilityHidden(!showsCopyButton) } if let plan = self.model.planText { Text(plan) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } private var liveSubtitle: MenuCardLiveSubtitle { let fallback = MenuCardLiveSubtitle(text: self.model.subtitleText, style: self.model.subtitleStyle) guard self.model.usesLiveSubtitle else { return fallback } return self.refreshMonitor?.subtitle(for: self.model.provider, fallback: fallback) ?? fallback } private func subtitleColor(for style: UsageMenuCardView.Model.SubtitleStyle) -> Color { switch style { case .info: MenuHighlightStyle.secondary(self.isHighlighted) case .loading: MenuHighlightStyle.secondary(self.isHighlighted) case .error: MenuHighlightStyle.error(self.isHighlighted) } } } private struct CopyIconButtonStyle: ButtonStyle { let isHighlighted: Bool func makeBody(configuration: Configuration) -> some View { configuration.label .padding(4) .background { RoundedRectangle(cornerRadius: 4, style: .continuous) .fill(MenuHighlightStyle.secondary(self.isHighlighted).opacity(configuration.isPressed ? 0.18 : 0)) } .scaleEffect(configuration.isPressed ? 0.94 : 1) .animation(.easeOut(duration: 0.12), value: configuration.isPressed) } } private struct CopyIconButton: View { let copyText: String let isHighlighted: Bool @State private var didCopy = false @State private var resetTask: Task<Void, Never>? var body: some View { Button { self.handleCopy() } label: { Image(systemName: self.didCopy ? "checkmark" : "doc.on.doc") .font(.caption2.weight(.semibold)) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .frame(width: 18, height: 18) } .buttonStyle(CopyIconButtonStyle(isHighlighted: self.isHighlighted)) .accessibilityLabel(self.didCopy ? L("Copied") : L("Copy error")) } private func handleCopy() { let text = self.copyText self.resetTask?.cancel() MenuPasteboardCopy.perform(text, completion: { self.didCopy = true self.resetTask = Task { @MainActor in try? await Task.sleep(for: .seconds(0.9)) self.didCopy = false } }) } } private struct ProviderCostContent: View { let section: UsageMenuCardView.Model.ProviderCostSection let progressColor: Color @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 6) { Text(self.section.title) .font(.body) .fontWeight(.medium) if let percentUsed = self.section.percentUsed { UsageProgressBar( percent: percentUsed, tint: self.progressColor, accessibilityLabel: L("Extra usage spent")) } HStack(alignment: .firstTextBaseline) { Text(self.section.spendLine).font(.footnote).lineLimit(1) Spacer() if let percentLine = self.section.percentLine { Text(percentLine) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } } private struct MetricRow: View { let metric: UsageMenuCardView.Model.Metric let title: String let progressColor: Color @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 6) { Text(self.title) .font(.body) .fontWeight(.medium) if let statusText = self.metric.statusText { Text(statusText) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } else { UsageProgressBar( percent: self.metric.percent, tint: self.progressColor, accessibilityLabel: self.metric.percentStyle.accessibilityLabel, pacePercent: self.metric.pacePercent, paceOnTop: self.metric.paceOnTop, warningMarkerPercents: self.metric.warningMarkerPercents) VStack(alignment: .leading, spacing: 2) { HStack(alignment: .firstTextBaseline) { Text(self.metric.percentLabel) .font(.footnote) .lineLimit(1) Spacer() if let rightLabel = self.metric.resetText { Text(rightLabel) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } if self.metric.detailLeftText != nil || self.metric.detailRightText != nil { HStack(alignment: .firstTextBaseline) { if let detailLeft = self.metric.detailLeftText { Text(detailLeft) .font(.footnote) .foregroundStyle(MenuHighlightStyle.primary(self.isHighlighted)) .lineLimit(1) } Spacer() if let detailRight = self.metric.detailRightText { Text(detailRight) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } .frame(maxWidth: .infinity, alignment: .leading) if let detail = self.metric.detailText { Text(detail) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(self.metric.cardStyle ? 10 : 0) .background(self.metric.cardStyle ? Color.secondary.opacity(self.isHighlighted ? 0.2 : 0.08) : Color.clear) .clipShape(RoundedRectangle(cornerRadius: self.metric.cardStyle ? 10 : 0)) } } private struct UsageNotesContent: View { let notes: [String] @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View {