refactor: redesign planning mode picker as 2x2 card grid
Replace crowded segmented control with clean card-based grid. Each card shows icon and label with selected state highlight. Fixes text truncation issue with "Follow Team" option. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -212,17 +212,25 @@ struct TripCreationView: View {
|
|||||||
|
|
||||||
private var planningModeSection: some View {
|
private var planningModeSection: some View {
|
||||||
ThemedSection(title: "How do you want to plan?") {
|
ThemedSection(title: "How do you want to plan?") {
|
||||||
Picker("Planning Mode", selection: $viewModel.planningMode) {
|
LazyVGrid(
|
||||||
|
columns: [
|
||||||
|
GridItem(.flexible(), spacing: Theme.Spacing.sm),
|
||||||
|
GridItem(.flexible(), spacing: Theme.Spacing.sm)
|
||||||
|
],
|
||||||
|
spacing: Theme.Spacing.sm
|
||||||
|
) {
|
||||||
ForEach(PlanningMode.allCases) { mode in
|
ForEach(PlanningMode.allCases) { mode in
|
||||||
Text(mode.displayName).tag(mode)
|
PlanningModeCard(
|
||||||
|
mode: mode,
|
||||||
|
isSelected: viewModel.planningMode == mode,
|
||||||
|
colorScheme: colorScheme
|
||||||
|
) {
|
||||||
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
|
viewModel.planningMode = mode
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.segmented)
|
|
||||||
|
|
||||||
Text(viewModel.planningMode.description)
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
|
||||||
.padding(.top, Theme.Spacing.xs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2384,6 +2392,55 @@ struct TeamPickerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Planning Mode Card
|
||||||
|
|
||||||
|
struct PlanningModeCard: View {
|
||||||
|
let mode: PlanningMode
|
||||||
|
let isSelected: Bool
|
||||||
|
let colorScheme: ColorScheme
|
||||||
|
let onTap: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: onTap) {
|
||||||
|
VStack(spacing: Theme.Spacing.sm) {
|
||||||
|
// Icon
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.fill(isSelected ? Theme.warmOrange : Theme.warmOrange.opacity(0.15))
|
||||||
|
.frame(width: 44, height: 44)
|
||||||
|
|
||||||
|
Image(systemName: mode.iconName)
|
||||||
|
.font(.system(size: 20, weight: .semibold))
|
||||||
|
.foregroundStyle(isSelected ? .white : Theme.warmOrange)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
|
Text(mode.displayName)
|
||||||
|
.font(.subheadline.weight(.medium))
|
||||||
|
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, Theme.Spacing.md)
|
||||||
|
.padding(.horizontal, Theme.Spacing.sm)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
|
||||||
|
.fill(Theme.cardBackgroundElevated(colorScheme))
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
|
||||||
|
.strokeBorder(
|
||||||
|
isSelected ? Theme.warmOrange : Color.clear,
|
||||||
|
lineWidth: 2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.accessibilityLabel("\(mode.displayName): \(mode.description)")
|
||||||
|
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct TeamRow: View {
|
struct TeamRow: View {
|
||||||
let team: Team
|
let team: Team
|
||||||
let isSelected: Bool
|
let isSelected: Bool
|
||||||
|
|||||||
Reference in New Issue
Block a user