feat(ui): add interactive MapKit region picker
Replace abstract colored rectangles with actual North America map using MapKit. Regions now follow state borders with tappable overlays for West/Central/East selection. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
// RegionMapSelector.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Interactive map for selecting travel regions.
|
||||
// Interactive map for selecting travel regions using MapKit.
|
||||
//
|
||||
|
||||
import MapKit
|
||||
import SwiftUI
|
||||
|
||||
/// A map-based selector for choosing geographic regions.
|
||||
/// Shows North America with three selectable zones: West, Central, East.
|
||||
/// Shows North America with three tappable overlay zones: West, Central, East.
|
||||
///
|
||||
/// Selection rules:
|
||||
/// - Can select: East, Central, West, East+Central, Central+West
|
||||
@@ -19,155 +20,148 @@ struct RegionMapSelector: View {
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
// Camera position centered on continental US
|
||||
@State private var cameraPosition: MapCameraPosition = .camera(
|
||||
MapCamera(
|
||||
centerCoordinate: CLLocationCoordinate2D(latitude: 39.5, longitude: -98.0),
|
||||
distance: 8_000_000,
|
||||
heading: 0,
|
||||
pitch: 0
|
||||
)
|
||||
)
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Theme.Spacing.sm) {
|
||||
// Map with regions
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// Background map outline
|
||||
mapBackground
|
||||
// Map with region overlays
|
||||
MapReader { proxy in
|
||||
Map(position: $cameraPosition, interactionModes: []) {
|
||||
// West region polygon
|
||||
MapPolygon(coordinates: RegionCoordinates.west)
|
||||
.foregroundStyle(fillColor(for: .west))
|
||||
.stroke(strokeColor(for: .west), lineWidth: strokeWidth(for: .west))
|
||||
|
||||
// Selectable regions
|
||||
HStack(spacing: 0) {
|
||||
regionButton(.west)
|
||||
regionButton(.central)
|
||||
regionButton(.east)
|
||||
// Central region polygon
|
||||
MapPolygon(coordinates: RegionCoordinates.central)
|
||||
.foregroundStyle(fillColor(for: .central))
|
||||
.stroke(strokeColor(for: .central), lineWidth: strokeWidth(for: .central))
|
||||
|
||||
// East region polygon
|
||||
MapPolygon(coordinates: RegionCoordinates.east)
|
||||
.foregroundStyle(fillColor(for: .east))
|
||||
.stroke(strokeColor(for: .east), lineWidth: strokeWidth(for: .east))
|
||||
}
|
||||
.mapStyle(.standard(elevation: .flat, pointsOfInterest: .excludingAll))
|
||||
.onTapGesture { location in
|
||||
if let coordinate = proxy.convert(location, from: .local) {
|
||||
let tappedRegion = regionForCoordinate(coordinate)
|
||||
onToggle(tappedRegion)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 140)
|
||||
.frame(height: 160)
|
||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
|
||||
.stroke(Theme.textMuted(colorScheme).opacity(0.5), lineWidth: 1)
|
||||
.stroke(Theme.textMuted(colorScheme).opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
|
||||
// Legend
|
||||
selectionLegend
|
||||
// Region legend with colors and example states
|
||||
regionLegend
|
||||
|
||||
// Selection footer
|
||||
selectionFooter
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Map Background
|
||||
// MARK: - Coordinate to Region
|
||||
|
||||
private var mapBackground: some View {
|
||||
ZStack {
|
||||
// Simple gradient background representing land
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color.green.opacity(0.15),
|
||||
Color.green.opacity(0.1),
|
||||
Color.green.opacity(0.15)
|
||||
],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
|
||||
// Subtle grid lines for visual separation
|
||||
HStack(spacing: 0) {
|
||||
Color.clear
|
||||
.frame(maxWidth: .infinity)
|
||||
Rectangle()
|
||||
.fill(Theme.textMuted(colorScheme).opacity(0.3))
|
||||
.frame(width: 1)
|
||||
Color.clear
|
||||
.frame(maxWidth: .infinity)
|
||||
Rectangle()
|
||||
.fill(Theme.textMuted(colorScheme).opacity(0.3))
|
||||
.frame(width: 1)
|
||||
Color.clear
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
/// Determines which region a coordinate falls into based on state groupings.
|
||||
/// West: States west of ~-102° (WA, OR, CA, NV, ID, UT, AZ, MT, WY, CO, NM)
|
||||
/// Central: States between ~-102° and ~-89° (ND, SD, NE, KS, OK, TX, MN, IA, MO, AR, LA, WI, IL)
|
||||
/// East: States east of ~-89°
|
||||
private func regionForCoordinate(_ coordinate: CLLocationCoordinate2D) -> Region {
|
||||
let longitude = coordinate.longitude
|
||||
if longitude < -102 {
|
||||
return .west
|
||||
} else if longitude > -89 {
|
||||
return .east
|
||||
} else {
|
||||
return .central
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Region Button
|
||||
// MARK: - Styling
|
||||
|
||||
private func regionButton(_ region: Region) -> some View {
|
||||
private func fillColor(for region: Region) -> Color {
|
||||
let isSelected = selectedRegions.contains(region)
|
||||
let isDisabled = isRegionDisabled(region)
|
||||
|
||||
return Button {
|
||||
onToggle(region)
|
||||
} label: {
|
||||
VStack(spacing: Theme.Spacing.xs) {
|
||||
// Region icon
|
||||
Image(systemName: iconForRegion(region))
|
||||
.font(.system(size: 24))
|
||||
.foregroundStyle(isSelected ? .white : Theme.textSecondary(colorScheme))
|
||||
|
||||
// Region name
|
||||
Text(region.shortName)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .semibold))
|
||||
.foregroundStyle(isSelected ? .white : Theme.textPrimary(colorScheme))
|
||||
|
||||
// Cities hint
|
||||
Text(citiesForRegion(region))
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.foregroundStyle(isSelected ? .white.opacity(0.8) : Theme.textMuted(colorScheme))
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(
|
||||
isSelected
|
||||
? regionColor(region)
|
||||
: Color.clear
|
||||
)
|
||||
.opacity(isDisabled ? 0.4 : 1.0)
|
||||
if isSelected {
|
||||
return regionColor(region).opacity(0.6)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(isDisabled)
|
||||
return Color.gray.opacity(0.15)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func iconForRegion(_ region: Region) -> String {
|
||||
switch region {
|
||||
case .west: return "sun.max.fill"
|
||||
case .central: return "building.2.fill"
|
||||
case .east: return "building.columns.fill"
|
||||
case .crossCountry: return "arrow.left.arrow.right"
|
||||
private func strokeColor(for region: Region) -> Color {
|
||||
let isSelected = selectedRegions.contains(region)
|
||||
if isSelected {
|
||||
return regionColor(region)
|
||||
}
|
||||
return Color.white.opacity(0.4)
|
||||
}
|
||||
|
||||
private func citiesForRegion(_ region: Region) -> String {
|
||||
switch region {
|
||||
case .west: return "LA, SF, Seattle"
|
||||
case .central: return "Chicago, Houston, Denver"
|
||||
case .east: return "NYC, Boston, Miami"
|
||||
case .crossCountry: return ""
|
||||
}
|
||||
private func strokeWidth(for region: Region) -> CGFloat {
|
||||
selectedRegions.contains(region) ? 3 : 1
|
||||
}
|
||||
|
||||
private func regionColor(_ region: Region) -> Color {
|
||||
switch region {
|
||||
case .west: return .orange
|
||||
case .central: return .blue
|
||||
case .east: return .green
|
||||
case .west: return Color(hex: "f97316") // Orange
|
||||
case .central: return Color(hex: "3b82f6") // Blue
|
||||
case .east: return Color(hex: "22c55e") // Green
|
||||
case .crossCountry: return .purple
|
||||
}
|
||||
}
|
||||
|
||||
/// East and West cannot both be selected (not adjacent)
|
||||
private func isRegionDisabled(_ region: Region) -> Bool {
|
||||
// If trying to show East+West as disabled, we handle that in toggle logic instead
|
||||
// This is for visual indication only
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Legend
|
||||
|
||||
private var selectionLegend: some View {
|
||||
private var regionLegend: some View {
|
||||
HStack(spacing: Theme.Spacing.md) {
|
||||
legendItem(.west, states: "CA, WA, OR, AZ")
|
||||
legendItem(.central, states: "TX, IL, CO, MN")
|
||||
legendItem(.east, states: "NY, FL, MA, GA")
|
||||
}
|
||||
}
|
||||
|
||||
private func legendItem(_ region: Region, states: String) -> some View {
|
||||
let isSelected = selectedRegions.contains(region)
|
||||
return HStack(spacing: 6) {
|
||||
Circle()
|
||||
.fill(regionColor(region))
|
||||
.frame(width: 10, height: 10)
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(Color.white.opacity(0.5), lineWidth: isSelected ? 2 : 0)
|
||||
)
|
||||
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(region.shortName)
|
||||
.font(.system(size: 11, weight: isSelected ? .bold : .medium))
|
||||
.foregroundStyle(isSelected ? regionColor(region) : Theme.textPrimary(colorScheme))
|
||||
|
||||
Text(states)
|
||||
.font(.system(size: 9))
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
private var selectionFooter: some View {
|
||||
HStack(spacing: Theme.Spacing.md) {
|
||||
if selectedRegions.isEmpty {
|
||||
Text("Tap regions to select")
|
||||
Text("Tap map to select regions")
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
} else {
|
||||
Text("Selected: \(selectedRegions.map { $0.shortName }.sorted().joined(separator: " + "))")
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
@@ -182,6 +176,159 @@ struct RegionMapSelector: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Region Coordinates
|
||||
|
||||
/// Geographic coordinates tracing actual state borders.
|
||||
/// West: WA, OR, CA, NV, ID, UT, AZ, MT, WY, CO, NM
|
||||
/// Central: ND, SD, NE, KS, OK, TX, MN, IA, MO, AR, LA, WI, IL
|
||||
/// East: All remaining states
|
||||
private enum RegionCoordinates {
|
||||
|
||||
// MARK: - West Region
|
||||
// Outer boundary of: WA, OR, CA, NV, ID, UT, AZ, MT, WY, CO, NM
|
||||
static let west: [CLLocationCoordinate2D] = [
|
||||
// Start: Canada/WA border, Pacific
|
||||
CLLocationCoordinate2D(latitude: 49.0, longitude: -123.3),
|
||||
// WA coast south
|
||||
CLLocationCoordinate2D(latitude: 46.3, longitude: -124.0),
|
||||
// OR coast
|
||||
CLLocationCoordinate2D(latitude: 42.0, longitude: -124.2),
|
||||
// CA coast
|
||||
CLLocationCoordinate2D(latitude: 39.0, longitude: -123.7),
|
||||
CLLocationCoordinate2D(latitude: 34.5, longitude: -120.5),
|
||||
CLLocationCoordinate2D(latitude: 32.5, longitude: -117.1),
|
||||
// CA/Mexico border
|
||||
CLLocationCoordinate2D(latitude: 32.7, longitude: -114.7),
|
||||
// AZ/Mexico border
|
||||
CLLocationCoordinate2D(latitude: 31.3, longitude: -111.1),
|
||||
// NM/Mexico border to TX border
|
||||
CLLocationCoordinate2D(latitude: 31.8, longitude: -108.2),
|
||||
CLLocationCoordinate2D(latitude: 32.0, longitude: -106.6),
|
||||
// NM/TX border north
|
||||
CLLocationCoordinate2D(latitude: 32.0, longitude: -103.0),
|
||||
// NM/OK/TX corner, then CO/OK border
|
||||
CLLocationCoordinate2D(latitude: 36.5, longitude: -103.0),
|
||||
// CO/KS border
|
||||
CLLocationCoordinate2D(latitude: 37.0, longitude: -102.0),
|
||||
CLLocationCoordinate2D(latitude: 40.0, longitude: -102.0),
|
||||
// CO/NE border to WY
|
||||
CLLocationCoordinate2D(latitude: 41.0, longitude: -102.0),
|
||||
// WY/NE/SD corner
|
||||
CLLocationCoordinate2D(latitude: 43.0, longitude: -104.0),
|
||||
// WY/SD border
|
||||
CLLocationCoordinate2D(latitude: 45.0, longitude: -104.0),
|
||||
// MT/ND border
|
||||
CLLocationCoordinate2D(latitude: 49.0, longitude: -104.0),
|
||||
// Canada border west
|
||||
CLLocationCoordinate2D(latitude: 49.0, longitude: -117.0),
|
||||
]
|
||||
|
||||
// MARK: - Central Region
|
||||
// Outer boundary of: ND, SD, NE, KS, OK, TX, MN, IA, MO, AR, LA, WI, IL
|
||||
static let central: [CLLocationCoordinate2D] = [
|
||||
// Start: Canada/ND border at MT
|
||||
CLLocationCoordinate2D(latitude: 49.0, longitude: -104.0),
|
||||
// MT/ND border south to SD
|
||||
CLLocationCoordinate2D(latitude: 45.9, longitude: -104.0),
|
||||
// WY/SD border
|
||||
CLLocationCoordinate2D(latitude: 45.0, longitude: -104.0),
|
||||
CLLocationCoordinate2D(latitude: 43.0, longitude: -104.0),
|
||||
// NE/WY border
|
||||
CLLocationCoordinate2D(latitude: 41.0, longitude: -104.0),
|
||||
// CO/NE border
|
||||
CLLocationCoordinate2D(latitude: 41.0, longitude: -102.0),
|
||||
// KS/CO border
|
||||
CLLocationCoordinate2D(latitude: 40.0, longitude: -102.0),
|
||||
CLLocationCoordinate2D(latitude: 37.0, longitude: -102.0),
|
||||
// OK/CO/NM corner
|
||||
CLLocationCoordinate2D(latitude: 36.5, longitude: -103.0),
|
||||
// TX/NM border
|
||||
CLLocationCoordinate2D(latitude: 32.0, longitude: -103.0),
|
||||
CLLocationCoordinate2D(latitude: 32.0, longitude: -106.6),
|
||||
// TX/Mexico border (Rio Grande)
|
||||
CLLocationCoordinate2D(latitude: 31.8, longitude: -106.4),
|
||||
CLLocationCoordinate2D(latitude: 29.5, longitude: -103.4),
|
||||
CLLocationCoordinate2D(latitude: 26.0, longitude: -97.4),
|
||||
// TX Gulf coast
|
||||
CLLocationCoordinate2D(latitude: 26.1, longitude: -97.2),
|
||||
CLLocationCoordinate2D(latitude: 29.4, longitude: -94.7),
|
||||
// LA Gulf coast
|
||||
CLLocationCoordinate2D(latitude: 29.5, longitude: -93.8),
|
||||
CLLocationCoordinate2D(latitude: 29.1, longitude: -89.0),
|
||||
// LA/MS border at Gulf
|
||||
CLLocationCoordinate2D(latitude: 30.2, longitude: -89.1),
|
||||
// MS/LA/AR corner, up to TN
|
||||
CLLocationCoordinate2D(latitude: 33.0, longitude: -91.0),
|
||||
// AR/TN border
|
||||
CLLocationCoordinate2D(latitude: 35.0, longitude: -90.3),
|
||||
CLLocationCoordinate2D(latitude: 36.5, longitude: -89.5),
|
||||
// IL eastern border (up to WI)
|
||||
CLLocationCoordinate2D(latitude: 37.0, longitude: -89.1),
|
||||
CLLocationCoordinate2D(latitude: 39.0, longitude: -87.5),
|
||||
CLLocationCoordinate2D(latitude: 41.7, longitude: -87.5),
|
||||
CLLocationCoordinate2D(latitude: 42.5, longitude: -87.5),
|
||||
// WI/MI border at Lake Michigan, north to Lake Superior
|
||||
CLLocationCoordinate2D(latitude: 45.0, longitude: -87.0),
|
||||
CLLocationCoordinate2D(latitude: 46.8, longitude: -87.5),
|
||||
CLLocationCoordinate2D(latitude: 46.5, longitude: -85.0),
|
||||
// Lake Superior north shore
|
||||
CLLocationCoordinate2D(latitude: 48.0, longitude: -89.5),
|
||||
// MN/Canada
|
||||
CLLocationCoordinate2D(latitude: 49.0, longitude: -89.5),
|
||||
// Canada border west to ND
|
||||
CLLocationCoordinate2D(latitude: 49.0, longitude: -97.2),
|
||||
]
|
||||
|
||||
// MARK: - East Region
|
||||
// Outer boundary of: MI, IN, OH, KY, TN, MS, AL, GA, FL, SC, NC, VA, WV, MD, DE, PA, NJ, NY, CT, RI, MA, VT, NH, ME
|
||||
static let east: [CLLocationCoordinate2D] = [
|
||||
// Start at western border (IL/IN, TN/AR, MS/LA line) going south
|
||||
CLLocationCoordinate2D(latitude: 42.5, longitude: -87.5), // WI/IL/IN corner (Lake Michigan)
|
||||
CLLocationCoordinate2D(latitude: 41.7, longitude: -87.5), // IL/IN border
|
||||
CLLocationCoordinate2D(latitude: 39.0, longitude: -87.5), // IN/IL border
|
||||
CLLocationCoordinate2D(latitude: 37.0, longitude: -89.1), // KY/IL/MO corner
|
||||
CLLocationCoordinate2D(latitude: 36.5, longitude: -89.5), // TN/AR/MO corner
|
||||
CLLocationCoordinate2D(latitude: 35.0, longitude: -90.3), // TN/AR/MS corner
|
||||
CLLocationCoordinate2D(latitude: 33.0, longitude: -91.0), // MS/LA/AR corner
|
||||
CLLocationCoordinate2D(latitude: 31.0, longitude: -89.7), // MS/LA border south
|
||||
CLLocationCoordinate2D(latitude: 30.2, longitude: -89.1), // MS Gulf coast
|
||||
// AL/FL Gulf coast
|
||||
CLLocationCoordinate2D(latitude: 30.2, longitude: -87.5), // AL coast
|
||||
CLLocationCoordinate2D(latitude: 30.0, longitude: -85.0), // FL panhandle
|
||||
// FL peninsula
|
||||
CLLocationCoordinate2D(latitude: 29.0, longitude: -83.0), // FL Gulf
|
||||
CLLocationCoordinate2D(latitude: 26.0, longitude: -82.0), // SW FL
|
||||
CLLocationCoordinate2D(latitude: 25.0, longitude: -80.5), // Miami
|
||||
// Atlantic coast north
|
||||
CLLocationCoordinate2D(latitude: 27.0, longitude: -80.0), // FL Atlantic
|
||||
CLLocationCoordinate2D(latitude: 30.5, longitude: -81.2), // Jacksonville
|
||||
CLLocationCoordinate2D(latitude: 32.0, longitude: -80.8), // Savannah
|
||||
CLLocationCoordinate2D(latitude: 34.0, longitude: -78.0), // NC coast
|
||||
CLLocationCoordinate2D(latitude: 36.5, longitude: -76.0), // VA coast
|
||||
CLLocationCoordinate2D(latitude: 38.5, longitude: -75.0), // DE coast
|
||||
CLLocationCoordinate2D(latitude: 40.0, longitude: -74.0), // NJ coast
|
||||
CLLocationCoordinate2D(latitude: 41.0, longitude: -72.0), // CT coast
|
||||
CLLocationCoordinate2D(latitude: 42.0, longitude: -70.5), // MA coast
|
||||
CLLocationCoordinate2D(latitude: 43.5, longitude: -70.0), // ME south
|
||||
CLLocationCoordinate2D(latitude: 45.0, longitude: -67.0), // ME coast
|
||||
CLLocationCoordinate2D(latitude: 47.2, longitude: -68.5), // ME north
|
||||
// Canada border west to Great Lakes
|
||||
CLLocationCoordinate2D(latitude: 47.0, longitude: -69.5), // ME/Canada
|
||||
CLLocationCoordinate2D(latitude: 45.0, longitude: -71.5), // VT/Canada
|
||||
CLLocationCoordinate2D(latitude: 45.0, longitude: -75.0), // NY/Canada
|
||||
CLLocationCoordinate2D(latitude: 43.5, longitude: -79.0), // Lake Ontario
|
||||
CLLocationCoordinate2D(latitude: 42.5, longitude: -79.0), // Lake Erie west
|
||||
CLLocationCoordinate2D(latitude: 41.5, longitude: -83.0), // OH/MI
|
||||
CLLocationCoordinate2D(latitude: 43.0, longitude: -82.5), // MI east
|
||||
CLLocationCoordinate2D(latitude: 46.0, longitude: -84.0), // Upper MI
|
||||
CLLocationCoordinate2D(latitude: 46.5, longitude: -85.0), // Lake Superior
|
||||
CLLocationCoordinate2D(latitude: 46.8, longitude: -87.5), // Upper MI west
|
||||
CLLocationCoordinate2D(latitude: 45.0, longitude: -87.0), // WI/MI (Green Bay)
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
struct PreviewWrapper: View {
|
||||
@State private var selected: Set<Region> = [.central]
|
||||
@@ -192,7 +339,7 @@ struct RegionMapSelector: View {
|
||||
if selected.contains(region) {
|
||||
selected.remove(region)
|
||||
} else {
|
||||
// Adjacency rule
|
||||
// Adjacency rule: can't select both East and West
|
||||
if region == .east {
|
||||
selected.remove(.west)
|
||||
} else if region == .west {
|
||||
@@ -211,3 +358,35 @@ struct RegionMapSelector: View {
|
||||
|
||||
return PreviewWrapper()
|
||||
}
|
||||
|
||||
#Preview("Dark Mode") {
|
||||
struct PreviewWrapper: View {
|
||||
@State private var selected: Set<Region> = [.west, .central]
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
RegionMapSelector(selectedRegions: $selected) { region in
|
||||
if selected.contains(region) {
|
||||
selected.remove(region)
|
||||
} else {
|
||||
if region == .east {
|
||||
selected.remove(.west)
|
||||
} else if region == .west {
|
||||
selected.remove(.east)
|
||||
}
|
||||
selected.insert(region)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
Text("Selected: \(selected.map { $0.shortName }.joined(separator: ", "))")
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(hex: "1a1a2e"))
|
||||
}
|
||||
}
|
||||
|
||||
return PreviewWrapper()
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user