mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
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:
@@ -9,6 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
70AC7B23011C802F750E470E /* Pods_Compound_Inspector.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2E28F45EEA9A8DE6AFB10E9 /* Pods_Compound_Inspector.framework */; };
|
||||
912974102A864F5E004BEA88 /* ListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9129740F2A864F5E004BEA88 /* ListScreen.swift */; };
|
||||
912EA03C2EF18F1F006485DF /* TitleAndIconScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 912EA03B2EF18F1D006485DF /* TitleAndIconScreen.swift */; };
|
||||
9132C62D2A72708400EAEF3D /* ActionSheetScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132C62C2A72708400EAEF3D /* ActionSheetScreen.swift */; };
|
||||
9132C62F2A72785500EAEF3D /* AlertScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132C62E2A72785400EAEF3D /* AlertScreen.swift */; };
|
||||
9132C6312A7280A500EAEF3D /* ContextMenuScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132C6302A7280A500EAEF3D /* ContextMenuScreen.swift */; };
|
||||
@@ -21,6 +22,7 @@
|
||||
917F562F28256DD800D19543 /* SidebarList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917F562E28256DD800D19543 /* SidebarList.swift */; };
|
||||
9184AC022825741600227C3A /* ScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9184AC012825741600227C3A /* ScreenContent.swift */; };
|
||||
9188246A2A2DE882001606AE /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918824692A2DE882001606AE /* Screen.swift */; };
|
||||
91A6CAA02EF1844E0090A2B4 /* BigIconScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91A6CA9F2EF184470090A2B4 /* BigIconScreen.swift */; };
|
||||
91B2EA9928CA05B900A90A83 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 91B2EA9828CA05B900A90A83 /* Compound */; };
|
||||
91B2EA9C28CA064800A90A83 /* ColorsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B2EA9B28CA064800A90A83 /* ColorsScreen.swift */; };
|
||||
91B946812A29062300F187EF /* IconsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B946802A29062300F187EF /* IconsScreen.swift */; };
|
||||
@@ -43,6 +45,7 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
9129740F2A864F5E004BEA88 /* ListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListScreen.swift; sourceTree = "<group>"; };
|
||||
912EA03B2EF18F1D006485DF /* TitleAndIconScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleAndIconScreen.swift; sourceTree = "<group>"; };
|
||||
9132C62C2A72708400EAEF3D /* ActionSheetScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetScreen.swift; sourceTree = "<group>"; };
|
||||
9132C62E2A72785400EAEF3D /* AlertScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertScreen.swift; sourceTree = "<group>"; };
|
||||
9132C6302A7280A500EAEF3D /* ContextMenuScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuScreen.swift; sourceTree = "<group>"; };
|
||||
@@ -59,6 +62,7 @@
|
||||
917F563028256DDC00D19543 /* TextFieldsScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldsScreen.swift; sourceTree = "<group>"; };
|
||||
9184AC012825741600227C3A /* ScreenContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenContent.swift; sourceTree = "<group>"; };
|
||||
918824692A2DE882001606AE /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
|
||||
91A6CA9F2EF184470090A2B4 /* BigIconScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigIconScreen.swift; sourceTree = "<group>"; };
|
||||
91AB476A2A0E774E00D961AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
91B2EA9728CA048200A90A83 /* compound-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "compound-ios"; path = ..; sourceTree = "<group>"; };
|
||||
91B2EA9B28CA064800A90A83 /* ColorsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorsScreen.swift; sourceTree = "<group>"; };
|
||||
@@ -170,9 +174,11 @@
|
||||
91B2EA9A28CA05F300A90A83 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
91A6CA9F2EF184470090A2B4 /* BigIconScreen.swift */,
|
||||
917F562A28256DCD00D19543 /* ButtonsScreen.swift */,
|
||||
9129740F2A864F5E004BEA88 /* ListScreen.swift */,
|
||||
917F563028256DDC00D19543 /* TextFieldsScreen.swift */,
|
||||
912EA03B2EF18F1D006485DF /* TitleAndIconScreen.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@@ -307,8 +313,10 @@
|
||||
917F561C28256DB800D19543 /* CompoundInspectorApp.swift in Sources */,
|
||||
9132C6332A72850200EAEF3D /* ShareSheetScreen.swift in Sources */,
|
||||
917C22F829C380A200B35660 /* FontsScreen.swift in Sources */,
|
||||
91A6CAA02EF1844E0090A2B4 /* BigIconScreen.swift in Sources */,
|
||||
9188246A2A2DE882001606AE /* Screen.swift in Sources */,
|
||||
9184AC022825741600227C3A /* ScreenContent.swift in Sources */,
|
||||
912EA03C2EF18F1F006485DF /* TitleAndIconScreen.swift in Sources */,
|
||||
912974102A864F5E004BEA88 /* ListScreen.swift in Sources */,
|
||||
9132C6312A7280A500EAEF3D /* ContextMenuScreen.swift in Sources */,
|
||||
9132C62F2A72785500EAEF3D /* AlertScreen.swift in Sources */,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct BigIconScreen: View {
|
||||
var body: some View {
|
||||
ScreenContent(navigationTitle: "Big Icon") {
|
||||
BigIcon_Previews.states
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BigIconScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
BigIconScreen()
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 700))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct TitleAndIconScreen: View {
|
||||
var body: some View {
|
||||
ScreenContent(navigationTitle: "Title & Icon") {
|
||||
TitleAndIcon_Previews.states
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TitleAndIconScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
TitleAndIconScreen()
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 700))
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ enum Screen: Hashable, View {
|
||||
case buttons
|
||||
/// Text field styles.
|
||||
case textFields
|
||||
/// The `BigIcon` component.
|
||||
case bigIcon
|
||||
|
||||
/// The system's confirmation dialog component
|
||||
case actionSheet
|
||||
@@ -53,6 +55,7 @@ enum Screen: Hashable, View {
|
||||
case .labels: EmptyView()
|
||||
case .buttons: ButtonsScreen()
|
||||
case .textFields: EmptyView()
|
||||
case .bigIcon: BigIconScreen()
|
||||
|
||||
case .actionSheet: ActionSheetScreen()
|
||||
case .alert: AlertScreen()
|
||||
|
||||
@@ -45,6 +45,12 @@ struct SidebarList: View {
|
||||
NavigationLink(value: Screen.list) {
|
||||
Label("List", systemImage: "list.bullet.clipboard")
|
||||
}
|
||||
NavigationLink(value: Screen.bigIcon) {
|
||||
Label("Big Icon", systemImage: "square.dashed")
|
||||
}
|
||||
NavigationLink(value: Screen.bigIcon) {
|
||||
Label("Title & Icon", systemImage: "richtext.page")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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") { })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated using Sourcery 2.2.6 — https://github.com/krzysztofzablocki/Sourcery
|
||||
// Generated using Sourcery 2.3.0 — https://github.com/krzysztofzablocki/Sourcery
|
||||
// DO NOT EDIT
|
||||
|
||||
// swiftlint:disable all
|
||||
@@ -11,6 +11,12 @@ extension PreviewTests {
|
||||
|
||||
// MARK: - PreviewProvider
|
||||
|
||||
func testBigIcon() async throws {
|
||||
for (index, preview) in BigIcon_Previews._allPreviews.enumerated() {
|
||||
try await assertSnapshots(matching: preview, step: index)
|
||||
}
|
||||
}
|
||||
|
||||
func testCompoundButtonStyle() async throws {
|
||||
for (index, preview) in CompoundButtonStyle_Previews._allPreviews.enumerated() {
|
||||
try await assertSnapshots(matching: preview, step: index)
|
||||
@@ -106,6 +112,12 @@ extension PreviewTests {
|
||||
try await assertSnapshots(matching: preview, step: index)
|
||||
}
|
||||
}
|
||||
|
||||
func testTitleAndIcon() async throws {
|
||||
for (index, preview) in TitleAndIcon_Previews._allPreviews.enumerated() {
|
||||
try await assertSnapshots(matching: preview, step: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:enable all
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:331ca045d1cf2a934949e4d036e6319cc3d2d7b7c6dfa8e0da72d4aea14131bc
|
||||
size 86689
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d3f5bf778a60fc5619035c4ffb6b77b27477261a2addf1a0da7e2c01b23451af
|
||||
size 45807
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:64e82affdf3abe32d17b942a3f8eda1b417c2c86c183b3584fbd1f17fd6c7ad4
|
||||
size 98586
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4a6e5b16fa2324399611d629a869d4eb15a085ae78de6275969f4d503a3e93d4
|
||||
size 56345
|
||||
Reference in New Issue
Block a user