// // Copyright 2025 Element Creations Ltd. // Copyright 2023-2025 New Vector Ltd. // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. // Please see LICENSE files in the repository root for full details. // import SwiftUI /// A view to be added on the trailing edge of a form row. public struct ListRowAccessory: View { @Environment(\.isEnabled) private var isEnabled enum Kind { /// A navigation chevron. case navigationLink /// A checkmark. case selected /// ``selected`` but invisible to reserve space. case unselected /// A circular checkmark. case multiSelected /// An empty circle. case multiUnselected var isMultiSelection: Bool { switch self { case .multiSelected, .multiUnselected: true default: false } } } /// A chevron to indicate that the button pushes another screen. public static var navigationLink: Self { Self(kind: .navigationLink) } /// A checkmark (or reserved space) to indicate that the row is selected. public static func selection(_ isSelected: Bool) -> Self { Self(kind: isSelected ? .selected : .unselected) } /// A circular checkmark (or empty circle) to indicate that the row is one of multiple selected. public static func multiSelection(_ isSelected: Bool) -> Self { Self(kind: isSelected ? .multiSelected : .multiUnselected) } let kind: Kind /// Negative padding added to prevent the accessory interfering with the row's padding. private var verticalPaddingFix: CGFloat { -4 } /// Absolute bodge until we have the circle icon in Compound. @ScaledMetric private var circleOverlayInsets = 5 public var body: some View { switch kind { case .navigationLink: CompoundIcon(\.chevronRight) .foregroundColor(.compound.iconTertiaryAlpha) .flipsForRightToLeftLayoutDirection(true) case .selected: CompoundIcon(\.check) .foregroundColor(isEnabled ? .compound.iconAccentPrimary : .compound.iconDisabled) .accessibilityAddTraits(.isSelected) .padding(.vertical, verticalPaddingFix) case .unselected: CompoundIcon(\.check) .hidden() .padding(.vertical, verticalPaddingFix) case .multiSelected: CompoundIcon(\.checkCircleSolid) .foregroundColor(isEnabled ? .compound.iconSuccessPrimary : .compound.iconDisabled) .accessibilityAddTraits(.isSelected) .padding(.vertical, verticalPaddingFix) case .multiUnselected: CompoundIcon(\.circle) .foregroundColor(isEnabled ? .compound.borderInteractivePrimary : .compound.borderDisabled) .padding(.vertical, verticalPaddingFix) } } } // MARK: - Previews struct ListRowAccessory_Previews: PreviewProvider, TestablePreview { static var previews: some View { Grid(alignment: .leading, verticalSpacing: 16) { row(title: "Navigation Link", accessory: .navigationLink) row(title: "Navigation Link disabled", accessory: .navigationLink) .disabled(true) row(title: "Selected", accessory: .selection(true)) row(title: "Selected disabled", accessory: .selection(true)) .disabled(true) row(title: "Unselected", accessory: .selection(false)) row(title: "Unselected disabled", accessory: .selection(false)) .disabled(true) row(title: "Multi-selected", accessory: .multiSelection(true)) row(title: "Multi-selected disabled", accessory: .multiSelection(true)) .disabled(true) row(title: "Multi-unselected", accessory: .multiSelection(false)) row(title: "Multi-unselected disabled", accessory: .multiSelection(false)) .disabled(true) } .previewDisplayName("Accessories") } static func row(title: String, accessory: ListRowAccessory) -> some View { GridRow { accessory Text(title) .foregroundStyle(.compound.textSecondary) } } }