feat(ios): unify action hierarchy and surfaces

Refine primary and toolbar actions, align row buttons, normalize status surfaces, and split storage from destructive cleanup. Verified with Swift tests and the simulator build.
This commit is contained in:
chick
2026-09-12 16:15:57 +08:00
parent 0f2866a853
commit 20632be86a
7 changed files with 220 additions and 123 deletions
@@ -11,8 +11,8 @@ public struct AICard<Content: View>: View {
public var body: some View {
content
.padding(padding)
.background(AITheme.surface)
.padding(padding)
.background(AICard.backgroundGradient)
.clipShape(RoundedRectangle(cornerRadius: AITheme.cardRadius, style: .continuous))
.shadow(color: .black.opacity(0.07), radius: 18, x: 0, y: 8)
.overlay {
@@ -22,6 +22,16 @@ public struct AICard<Content: View>: View {
}
}
private extension AICard {
static var backgroundGradient: LinearGradient {
LinearGradient(
colors: [AITheme.surfaceRaised, AITheme.surface],
startPoint: .top,
endPoint: .bottom
)
}
}
public struct AITag: View {
private let text: String
private let tint: Color
@@ -33,7 +43,7 @@ public struct AITag: View {
public var body: some View {
Text(text)
.font(.system(size: 12, weight: .semibold))
.font(.caption2.weight(.semibold))
.foregroundStyle(tint)
.lineLimit(1)
.truncationMode(.middle)
@@ -100,6 +110,7 @@ public struct AISectionTitle: View {
Image(systemName: systemImage)
.font(.subheadline.weight(.semibold))
.foregroundStyle(AITheme.primary)
.frame(width: 22, height: 22)
.accessibilityHidden(true)
Text(title)
.font(.headline.weight(.semibold))
@@ -152,6 +163,8 @@ public struct AIStatCard: View {
VStack(alignment: .leading, spacing: 5) {
Text(title)
.font(.caption.weight(.semibold))
.lineLimit(1)
.minimumScaleFactor(0.85)
.foregroundStyle(AITheme.muted)
Text(value)
.font(.title3.weight(.bold))
@@ -194,9 +207,9 @@ public struct AIStatusBanner: View {
}
.padding(15)
.background(tint.opacity(0.10))
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
.clipShape(RoundedRectangle(cornerRadius: AITheme.cardRadius - 6, style: .continuous))
.overlay {
RoundedRectangle(cornerRadius: 18, style: .continuous)
RoundedRectangle(cornerRadius: AITheme.cardRadius - 6, style: .continuous)
.strokeBorder(tint.opacity(0.22))
}
.accessibilityElement(children: .combine)
@@ -282,7 +295,7 @@ public struct AIListCard<Footer: View>: View {
Text(subtitle)
.font(.caption)
.foregroundStyle(AITheme.muted)
.lineLimit(1)
.lineLimit(2)
.truncationMode(.middle)
.frame(maxWidth: .infinity, alignment: .leading)
}
@@ -318,12 +331,28 @@ public struct AILoadingCard: View {
VStack(spacing: 10) {
ProgressView()
.controlSize(.regular)
.frame(width: 44, height: 44)
.background(AITheme.control)
.clipShape(Circle())
Text(message)
.font(.subheadline)
.foregroundStyle(AITheme.muted)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 18)
.padding(16)
.background(
LinearGradient(
colors: [AITheme.surfaceRaised, AITheme.surface],
startPoint: .top,
endPoint: .bottom
)
)
.clipShape(RoundedRectangle(cornerRadius: AITheme.cardRadius, style: .continuous))
.shadow(color: .black.opacity(0.07), radius: 18, x: 0, y: 8)
.overlay {
RoundedRectangle(cornerRadius: AITheme.cardRadius, style: .continuous)
.strokeBorder(AITheme.line)
}
.accessibilityElement(children: .combine)
}
}
@@ -367,9 +396,10 @@ private struct AIEqualWidthLayout: Layout {
}
let equalWidth = max(0, (width - spacing * CGFloat(count - 1)) / CGFloat(count))
let height = subviews.map {
$0.sizeThatFits(ProposedViewSize(width: equalWidth, height: nil)).height
}.max() ?? 0
let height =
subviews.map {
$0.sizeThatFits(ProposedViewSize(width: equalWidth, height: nil)).height
}.max() ?? 0
return CGSize(width: width, height: height)
}
@@ -454,7 +484,7 @@ public struct AISegmentedControl<Value: Hashable>: View {
selection = option.value
} label: {
Text(option.title)
.font(.subheadline.weight(isSelected ? .semibold : .medium))
.font(.body.weight(isSelected ? .semibold : .medium))
.lineLimit(1)
.minimumScaleFactor(0.75)
.foregroundStyle(isSelected ? AITheme.onPrimary : AITheme.muted)
@@ -462,11 +492,18 @@ public struct AISegmentedControl<Value: Hashable>: View {
.background {
if isSelected {
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(AITheme.primary)
.fill(
LinearGradient(
colors: [AITheme.primaryTop, AITheme.primaryBottom],
startPoint: .top,
endPoint: .bottom
)
)
}
}
}
.buttonStyle(.plain)
.contentShape(Rectangle())
.accessibilityLabel(option.title)
.accessibilityAddTraits(isSelected ? .isSelected : [])
}
@@ -516,6 +553,7 @@ public struct AISearchField: View {
public var body: some View {
HStack(spacing: 6) {
Image(systemName: "magnifyingglass")
.font(.system(size: 15, weight: .medium))
.foregroundStyle(AITheme.muted)
.accessibilityHidden(true)
TextField(placeholder, text: $text)
@@ -627,7 +665,7 @@ public struct AIToolbarButton: View {
.font(.system(size: 15, weight: .semibold))
}
}
.buttonStyle(AIButtonStyle(kind: kind, size: .toolbar))
.buttonStyle(AIButtonStyle(kind: kind, size: .toolbarInline))
.accessibilityLabel(title)
}
}