123 lines
4.4 KiB
Swift
123 lines
4.4 KiB
Swift
//
|
|
// 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)
|
|
}
|
|
}
|
|
}
|