Move BigIcon into Compound and add a new TitleAndIcon component. (#4866)

* Move BigIcon into Compound.

* Replace the old server selection image asset with the \.host icon.

* Add a new TitleAndIcon component to Compound.

* Add the ability to override TitleAndIcon's padding.
This commit is contained in:
Doug
2025-12-16 16:01:26 +00:00
committed by GitHub
parent b668de8908
commit 85ebf4d1ce
47 changed files with 280 additions and 144 deletions
@@ -13,7 +13,7 @@ public struct SendButton: View {
@Environment(\.colorScheme) private var colorScheme
/// The action to perform when the user triggers the button.
public let action: () -> Void
private let action: () -> Void
private var iconColor: Color {
guard isEnabled else { return .compound.iconQuaternary }
@@ -0,0 +1,125 @@
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-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
/// An image that is styled for use as the main/top/hero screen icon. This component
/// takes a compound icon. If you would like to apply it to an SFSymbol, you can call
/// the `bigIcon()` modifier directly on the Image.
public struct BigIcon: View {
public enum Style {
case defaultSolid
case `default`
case alertSolid
case alert
case successSolid
case success
var foregroundColor: Color {
switch self {
case .defaultSolid, .default:
.compound.iconSecondary
case .alertSolid, .alert:
.compound.iconCriticalPrimary
case .successSolid, .success:
.compound.iconSuccessPrimary
}
}
var backgroundFillColor: Color {
switch self {
case .defaultSolid:
.compound.bgSubtleSecondary
case .alertSolid:
.compound.bgCriticalSubtle
case .successSolid:
.compound.bgSuccessSubtle
case .default, .alert, .success:
.compound.bgCanvasDefault
}
}
}
/// The icon that is shown.
private let icon: KeyPath<CompoundIcons, Image>
private let style: Style
public init(icon: KeyPath<CompoundIcons, Image>, style: Style = .defaultSolid) {
self.icon = icon
self.style = style
}
public var body: some View {
CompoundIcon(icon, size: .custom(32), relativeTo: .compound.headingLG)
.modifier(BigIconModifier(style: style))
}
}
public extension Image {
/// Styles the image for use as the main/top/hero screen icon. You should prefer
/// the BigIcon component when possible, by using an icon from Compound.
func bigIcon(insets: CGFloat = 16, style: BigIcon.Style = .defaultSolid) -> some View {
resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.scaledPadding(insets, relativeTo: .compound.headingLG)
.modifier(BigIconModifier(style: style))
}
}
private struct BigIconModifier: ViewModifier {
let style: BigIcon.Style
func body(content: Content) -> some View {
content
.scaledFrame(size: 64, relativeTo: .compound.headingLG)
.foregroundColor(style.foregroundColor)
.background {
RoundedRectangle(cornerRadius: 14)
.fill(style.backgroundFillColor)
}
.accessibilityHidden(true)
}
}
// MARK: - Previews
public struct BigIcon_Previews: PreviewProvider, TestablePreview {
public static var previews: some View {
VStack(spacing: 40) {
HStack(spacing: 20) {
BigIcon(icon: \.lockSolid)
Image(systemName: "hourglass")
.bigIcon()
Image(systemSymbol: .serverRack)
.bigIcon(insets: 19)
}
states
}
}
public static var states: some View {
VStack(spacing: 20) {
HStack(spacing: 20) {
BigIcon(icon: \.helpSolid)
BigIcon(icon: \.helpSolid, style: .default)
}
HStack(spacing: 20) {
BigIcon(icon: \.errorSolid, style: .alertSolid)
BigIcon(icon: \.errorSolid, style: .alert)
}
HStack(spacing: 20) {
BigIcon(icon: \.checkCircleSolid, style: .successSolid)
BigIcon(icon: \.checkCircleSolid, style: .success)
}
}
}
}
@@ -0,0 +1,103 @@
//
// Copyright 2025 Element Creations 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
/// Our standard screen header that includes a title and icon along with
/// optional subtitle and button components.
///
/// **Note:** By default this component includes the standard padding
/// specified in the Figma, however you may need to customise it using
/// the `insets` parameter.
public struct TitleAndIcon: View {
private let title: String
private let subtitle: String?
private let icon: KeyPath<CompoundIcons, Image>
private let iconStyle: BigIcon.Style
private let button: ButtonDetails?
private let insets: EdgeInsets
public init(title: String,
subtitle: String? = nil,
icon: KeyPath<CompoundIcons, Image>,
iconStyle: BigIcon.Style,
button: ButtonDetails? = nil,
insets: EdgeInsets = .init(top: 24, leading: 24, bottom: 40, trailing: 24)) {
self.title = title
self.subtitle = subtitle
self.icon = icon
self.iconStyle = iconStyle
self.button = button
self.insets = insets
}
public var body: some View {
VStack(spacing: 16) {
BigIcon(icon: icon, style: iconStyle)
VStack(spacing: 8) {
Text(title)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
if let subtitle {
Text(subtitle)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
}
}
if let button {
Button(button.title, action: button.action)
.buttonStyle(.compound(.tertiary, size: .small))
}
}
.padding(insets)
}
}
public extension TitleAndIcon {
/// Everything required to construct the `TitleAndIcon` view's optional button.
struct ButtonDetails {
public let title: String
public let action: () -> Void
init(title: String, action: @escaping () -> Void) {
self.title = title
self.action = action
}
}
}
// MARK: - Previews
public struct TitleAndIcon_Previews: PreviewProvider, TestablePreview {
public static var previews: some View {
states
}
public static var states: some View {
VStack(spacing: 20) {
TitleAndIcon(title: "Headline",
icon: \.circle,
iconStyle: .defaultSolid)
TitleAndIcon(title: "Headline",
subtitle: "Description goes here",
icon: \.circle,
iconStyle: .defaultSolid)
TitleAndIcon(title: "Headline",
subtitle: "Description goes here",
icon: \.circle,
iconStyle: .defaultSolid,
button: .init(title: "Learn more") { })
}
}
}