Move Compound Inspector into the project.

This commit is contained in:
Doug
2025-09-28 15:37:59 +01:00
committed by Doug
parent 90eeabf9de
commit c6b6c13533
33 changed files with 1828 additions and 0 deletions
@@ -0,0 +1,27 @@
//
// Copyright 2023, 2024 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 Compound
import SwiftUI
struct ButtonsScreen: View {
var body: some View {
ScreenContent(navigationTitle: "Buttons") {
CompoundButtonStyle_Previews.states
SendButton_Previews.states
}
}
}
struct ButtonsScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ButtonsScreen()
}
.previewLayout(.fixed(width: 375, height: 700))
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2023, 2024 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
import Compound
import HyperionCore
struct ListScreen: View {
var body: some View {
ListRow_Previews.previews
.navigationTitle("Lists")
}
}
struct ListScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ListScreen()
}
.previewLayout(.fixed(width: 375, height: 700))
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2023, 2024 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
import DesignKit
struct TextFieldsScreen: View {
var body: some View {
ScreenContent(navigationTitle: "Text Fields") {
BorderedInputFieldStyle_Previews.states
}
}
}
struct TextFieldsScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
TextFieldsScreen()
}
.previewLayout(.fixed(width: 375, height: 700))
}
}
@@ -0,0 +1,87 @@
//
// Copyright 2023, 2024 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
import Compound
import HyperionCore
@main
struct CompoundInspectorApp: App {
@State private var colorScheme: ColorScheme = .light
@State private var dynamicTypeSize: DynamicTypeSize = .large
private var isDark: Bool { colorScheme == .dark }
private var preferredColorScheme: ColorScheme? { ProcessInfo.processInfo.isMacCatalystApp ? colorScheme : nil }
var body: some Scene {
WindowGroup {
NavigationSplitView {
SidebarList()
.navigationTitle("Components")
.navigationDestination(for: Screen.self) { screen in
screen
#if targetEnvironment(macCatalyst)
.dynamicTypeSize(dynamicTypeSize)
#endif
.navigationBarTitleDisplayMode(.inline)
.toolbar { screenToolbar }
}
} detail: {
EmptyView()
}
.accentColor(.compound.textActionPrimary)
.preferredColorScheme(preferredColorScheme)
.introspect(.window, on: .supportedVersions) { window in
// Apply the tint colour to alerts and confirmation dialogs
window.tintColor = .compound.textActionPrimary
}
}
.commands {
CommandMenu("Options") {
Button("Hyperion", action: HyperionManager.sharedInstance().togglePluginDrawer)
.keyboardShortcut("i", modifiers: [.command, .option])
Divider()
Button("Toggle Appearance", action: toggleDarkMode)
.keyboardShortcut("a", modifiers: [.command, .shift])
#if targetEnvironment(macCatalyst)
textSizePicker
#endif
}
}
}
var textSizePicker: some View {
Picker("Text Size", selection: $dynamicTypeSize) {
ForEach(DynamicTypeSize.allCases, id: \.self) { size in
Text(String(describing: size)).tag(size)
}
}
}
@ViewBuilder
var screenToolbar: some View {
#if targetEnvironment(macCatalyst)
Menu {
textSizePicker
.pickerStyle(.inline)
} label: {
Image(systemName: "textformat.size")
}
#endif
Button(action: HyperionManager.sharedInstance().togglePluginDrawer) {
Image(systemName: "ruler")
}
}
func toggleDarkMode() {
colorScheme = isDark ? .light : .dark
}
}
@@ -0,0 +1,63 @@
//
// Copyright 2023, 2024 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 screen of components/tokens that can be navigated to.
enum Screen: Hashable, View {
/// Colour tokens
case colors
/// Gradients assembled from colour tokens
case gradients
/// Font tokens
case fonts
/// Icon tokens
case icons
/// Size tokens
case sizes
/// List styles and components
case list
/// Label styles
case labels
/// Button styles
case buttons
/// Text field styles.
case textFields
/// The system's confirmation dialog component
case actionSheet
/// The system's alert component
case alert
/// The system's context menu component
case contextMenu
/// The system's navigation bar component
case navigationBar
/// The system's share sheet component
case shareSheet
var body: some View {
switch self {
case .colors: ColorsScreen()
case .gradients: GradientsScreen()
case .fonts: FontsScreen()
case .icons: IconsScreen()
case .list: ListScreen()
case .sizes: EmptyView()
case .labels: EmptyView()
case .buttons: ButtonsScreen()
case .textFields: EmptyView()
case .actionSheet: ActionSheetScreen()
case .alert: AlertScreen()
case .contextMenu: ContextMenuScreen()
case .navigationBar: NavigationBarScreen()
case .shareSheet: ShareSheetScreen()
}
}
}
@@ -0,0 +1,34 @@
//
// Copyright 2023, 2024 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 helper to provider a default layout for the content of a screen.
struct ScreenContent<Content: View>: View {
let navigationTitle: String
@ViewBuilder var content: () -> Content
var body: some View {
ScrollView {
LazyVStack(spacing: 16) {
content()
}
.padding()
}
.navigationTitle(navigationTitle)
}
}
struct ComponentsContainer_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ScreenContent(navigationTitle: "Buttons") {
Button("Confirm") { }
}
}
}
}
@@ -0,0 +1,81 @@
//
// Copyright 2023, 2024 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
struct SidebarList: View {
var body: some View {
List {
tokensSection
componentsSection
systemComponentsSection
}
.navigationTitle("Components")
.listStyle(.sidebar)
.tint(.compound.iconAccentTertiary)
}
var tokensSection: some View {
Section("Tokens") {
NavigationLink(value: Screen.colors) {
Label("Colours", systemImage: "paintpalette")
}
NavigationLink(value: Screen.gradients) {
Label("Gradients", systemImage: "lightspectrum.horizontal")
}
NavigationLink(value: Screen.fonts) {
Label("Typography", systemImage: "character")
}
NavigationLink(value: Screen.icons) {
Label("Icons", systemImage: "pencil.and.outline")
}
}
}
var componentsSection: some View {
Section("Components") {
NavigationLink(value: Screen.buttons) {
Label("Buttons", systemImage: "rectangle.and.hand.point.up.left")
}
NavigationLink(value: Screen.list) {
Label("List", systemImage: "list.bullet.clipboard")
}
}
}
var systemComponentsSection: some View {
Section("System Components") {
NavigationLink(value: Screen.actionSheet) {
Label("Action Sheets", systemImage: "window.shade.closed")
}
NavigationLink(value: Screen.alert) {
Label("Alerts", systemImage: "exclamationmark.triangle")
}
NavigationLink(value: Screen.contextMenu) {
Label("Context Menus", systemImage: "contextualmenu.and.cursorarrow")
}
NavigationLink(value: Screen.navigationBar) {
Label("Navigation Bar", systemImage: "window.shade.open")
}
NavigationLink(value: Screen.shareSheet) {
Label("Share Sheet", systemImage: "square.and.arrow.up")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
SidebarList()
}
}
}
@@ -0,0 +1,53 @@
//
// Copyright 2023, 2024 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
import Compound
struct ActionSheetScreen: View {
@State private var isPresentingConfirmationDialog = true
var body: some View {
ScreenContent(navigationTitle: "Action Sheets") {
Text("This component will be rendered differently when running on macOS.")
.font(.footnote)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
}
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button("Show Sheet") {
isPresentingConfirmationDialog = true
}
.confirmationDialog("A Short Title is Best",
isPresented: $isPresentingConfirmationDialog) {
Button("Default") { }
Button("Default") { }
Button("Default") { }
Button("Disabled") { }
.disabled(true) // This hides the button on iOS 16 😐
Button("Destructive", role: .destructive) { }
} message: {
Text("A message should be a short, complete sentence.")
}
}
}
}
}
struct ActionSheetScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ActionSheetScreen()
}
.introspect(.window, on: .supportedVersions) { window in
// Fix the tint colour like the App strut does.
window.tintColor = .compound.textActionPrimary
}
.previewLayout(.fixed(width: 375, height: 750))
}
}
@@ -0,0 +1,79 @@
//
// Copyright 2023, 2024 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
import Compound
struct AlertScreen: View {
@State private var isPresentingActionAlert = false
@State private var isPresentingActionAndCancelAlert = false
@State private var isPresentingDestructiveAlert = false
@State private var isPresentingTextFieldAlert = false
@State private var textFieldValue = ""
private let title = "A Short Title is Best"
private let message = "A message should be a short, complete sentence."
var body: some View {
ScreenContent(navigationTitle: "Alerts") {
Text("This component will be rendered differently when running on macOS.")
.font(.footnote)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
Button("Action") { isPresentingActionAlert = true }
.padding(.top)
.alert(title, isPresented: $isPresentingActionAlert) {
Button("Action") { }
} message: {
Text(message)
}
Button("Action and Cancel") { isPresentingActionAndCancelAlert = true }
.padding(.top)
.alert(title, isPresented: $isPresentingActionAndCancelAlert) {
Button("Action") { }
Button("Cancel", role: .cancel) { }
} message: {
Text(message)
}
Button("Destructive") { isPresentingDestructiveAlert = true }
.padding(.top)
.alert(title, isPresented: $isPresentingDestructiveAlert) {
Button("Action") { }
Button("Destructive", role: .destructive) { }
Button("Cancel", role: .cancel) { }
} message: {
Text(message)
}
Button("TextField") { isPresentingTextFieldAlert = true }
.padding(.top)
.alert(title, isPresented: $isPresentingTextFieldAlert) {
TextField("Input", text: $textFieldValue, prompt: Text("Placeholder"))
Button("Action") { }
Button("Cancel", role: .cancel) { }
} message: {
Text(message)
}
}
}
}
struct AlertScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
AlertScreen()
}
.introspect(.window, on: .supportedVersions) { window in
// Fix the tint colour like the App strut does.
window.tintColor = .compound.textActionPrimary
}
.previewLayout(.fixed(width: 375, height: 750))
}
}
@@ -0,0 +1,84 @@
//
// Copyright 2023, 2024 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
import Compound
struct ContextMenuScreen: View {
var body: some View {
ScreenContent(navigationTitle: "Context Menus") {
Text("This component will be rendered differently when running on macOS.")
.font(.footnote)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
Text("Actions")
.padding()
.contentShape(.contextMenuPreview, RoundedRectangle(cornerRadius: 12))
.contextMenu {
Section {
Button { } label: {
Label("Action", systemImage: "square.dashed")
}
Button { } label: {
Label("Action", systemImage: "square.dashed")
}
Button { } label: {
Label("Disabled", systemImage: "square.dashed")
}
.disabled(true)
Button(role: .destructive) { } label: {
Label("Destructive", systemImage: "square.dashed")
}
}
Section {
Button { } label: {
Label("Action", systemImage: "square.dashed")
}
Button { } label: {
Label("Action", systemImage: "square.dashed")
}
}
}
Text("Toggles")
.padding()
.contentShape(.contextMenuPreview, RoundedRectangle(cornerRadius: 12))
.contextMenu {
Section {
Toggle(isOn: .constant(true)) {
Label("Toggle", systemImage: "square.dashed")
}
Toggle(isOn: .constant(false)) {
Label("Toggle", systemImage: "square.dashed")
}
Toggle(isOn: .constant(false)) {
Label("Disabled", systemImage: "square.dashed")
}
.disabled(true)
}
Section {
Toggle(isOn: .constant(true)) {
Label("Toggle", systemImage: "square.dashed")
}
Toggle(isOn: .constant(true)) {
Label("Toggle", systemImage: "square.dashed")
}
}
}
}
}
}
struct ContextMenuScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ContextMenuScreen()
}
.previewLayout(.fixed(width: 375, height: 750))
}
}
@@ -0,0 +1,110 @@
//
// Copyright 2023, 2024 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
import Compound
struct NavigationBarScreen: View {
enum TitleMode {
case large
case inline
case hidden
}
enum BackButtonMode {
case navigation
case cancellationAction
case hidden
}
@State private var titleMode = TitleMode.inline
@State private var backButtonMode = BackButtonMode.navigation
@State private var hasConfirmationAction = true
@State private var hasPrimaryAction = false
var body: some View {
Form {
Section {
ListRow(kind: .custom {
Text("This component may be rendered differently when running on macOS.")
.font(.compound.bodySM)
.foregroundColor(.compound.textSecondary)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.listRowBackground(Color.clear)
})
}
Section {
ListRow(label: .plain(title: "Title"),
kind: .picker(selection: $titleMode, items: [
(title: "Large", tag: .large),
(title: "Inline", tag: .inline),
(title: "Hidden", tag: .hidden)
]))
ListRow(label: .plain(title: "Back Button"),
kind: .picker(selection: $backButtonMode, items: [
(title: "Navigation", tag: .navigation),
(title: "Cancelation Action", tag: .cancellationAction),
(title: "Hidden", tag: .hidden)
]))
ListRow(label: .plain(title: "Confirmation Action"),
kind: .toggle($hasConfirmationAction))
ListRow(label: .plain(title: "Primary Action"),
kind: .toggle($hasPrimaryAction))
} header: {
Text("Configuration")
.compoundListSectionHeader()
}
Section {
ListRow(kind: .custom {
VStack {
Text("Empty section to make the form scrollable")
.font(.compound.bodySM)
.foregroundColor(.compound.textSecondary)
Spacer(minLength: 500)
}
.padding(ListRowPadding.insets)
})
}
}
.compoundList()
.navigationTitle(titleMode == .hidden ? "" : "Navigation Bar")
.navigationBarTitleDisplayMode(titleMode == .large ? .large : .inline)
.navigationBarBackButtonHidden(backButtonMode != .navigation)
.toolbar {
if hasConfirmationAction {
ToolbarItem(placement: .confirmationAction) {
Button("Confirm") { }
}
}
if hasPrimaryAction {
ToolbarItem(placement: .primaryAction) {
Button("Primary") { }
}
}
if backButtonMode == .cancellationAction {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { }
}
}
}
}
}
struct NavigationBarScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
NavigationBarScreen()
}
.previewLayout(.fixed(width: 375, height: 750))
}
}
@@ -0,0 +1,32 @@
//
// Copyright 2023, 2024 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
import Compound
struct ShareSheetScreen: View {
var body: some View {
ScreenContent(navigationTitle: "Share Sheets") {
Text("This component will be rendered differently when running on macOS.")
.font(.footnote)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
ShareLink(item: URL(string: "https://element.io")!, subject: Text("The subject"), message: Text("The message"))
.padding(.top)
}
}
}
struct ShareSheetScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ShareSheetScreen()
}
.previewLayout(.fixed(width: 375, height: 750))
}
}
@@ -0,0 +1,66 @@
//
// Copyright 2023, 2024 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 Compound
import SwiftUI
protocol AllValues {
associatedtype ValueType
var allValues: [(name: String, value: ValueType)] { get }
}
extension AllValues {
var allValues: [(name: String, value: ValueType)] {
var values: [(name: String, value: ValueType)] = []
let mirror = Mirror(reflecting: self)
for property in mirror.children {
if let label = property.label, let value = property.value as? ValueType {
values.append((label, value))
}
}
return values
}
}
extension CompoundColors: AllValues { typealias ValueType = Color }
extension CompoundColorTokens: AllValues { typealias ValueType = Color }
extension CompoundGradients: AllValues { typealias ValueType = Gradient }
extension CompoundFonts: AllValues { typealias ValueType = Font }
extension CompoundIcons: AllValues { typealias ValueType = Image }
extension CompoundColors {
var allColors: [(name: String, value: Color)] {
CompoundColorTokens().allValues + allValues
}
}
extension CompoundIcons {
var allKeyPaths: [(name: String, value: KeyPath<CompoundIcons, Image>)] {
var icons: [(name: String, value: KeyPath<CompoundIcons, Image>)] = []
let mirror = Mirror(reflecting: self)
for property in mirror.children {
if let label = property.label {
let keyPath = \CompoundIcons.[checkedMirrorDescendant: label] as PartialKeyPath
if let imageKeyPath = keyPath as? KeyPath<CompoundIcons, Image> {
icons.append((label, imageKeyPath))
} else {
print("Nope")
}
}
}
return icons
}
private subscript(checkedMirrorDescendant key: String) -> Any {
return Mirror(reflecting: self).descendant(key)!
}
}
@@ -0,0 +1,85 @@
//
// Copyright 2023, 2024 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
import Compound
struct ColorsScreen: View {
var body: some View {
ScreenContent(navigationTitle: "Colors") {
ForEach(Color.compound.allColors, id: \.name) { color in
ColorItem(color: color.value, name: color.name)
}
}
}
}
struct ColorItem: View {
let color: Color
let name: String
var body: some View {
HStack {
swatch
VStack(alignment: .leading) {
Text(name)
.font(.compound.bodyLG)
.foregroundColor(.compound.textPrimary)
Text(color.hexValue())
.font(.compound.bodySM.monospaced())
.foregroundColor(.compound.textSecondary)
}
}
}
var swatch: some View {
swatchShape
.foregroundColor(color)
.frame(height: 40)
.aspectRatio(1, contentMode: .fit)
.overlay {
swatchShape
.strokeBorder(Color.compound.iconPrimary, lineWidth: 1.5)
.opacity(0.2)
}
}
var swatchShape: some InsettableShape {
RoundedRectangle(cornerRadius: 12, style: .continuous)
}
}
private extension Color {
func hexValue() -> String {
let uiColor = UIColor(self)
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return "#\(red.asHex)\(green.asHex)\(blue.asHex)"
}
}
private extension CGFloat {
var asHex: String {
String(format:"%02X", Int((self * 255).rounded()))
}
}
struct ColorsScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ColorsScreen()
}
.previewLayout(.fixed(width: 375, height: 700))
}
}
@@ -0,0 +1,40 @@
//
// Copyright 2023, 2024 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
import Compound
struct FontsScreen: View {
var body: some View {
ScreenContent(navigationTitle: "Fonts") {
ForEach(Font.compound.allValues, id: \.name) { font in
FontItem(font: font.value, name: font.name)
}
}
}
}
struct FontItem: View {
let font: Font
let name: String
var body: some View {
Text(name)
.font(font)
.foregroundColor(.compound.textPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
struct FontsScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
FontsScreen()
}
.previewLayout(.fixed(width: 375, height: 750))
}
}
@@ -0,0 +1,61 @@
//
// Copyright 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
import Compound
struct GradientsScreen: View {
var body: some View {
ScreenContent(navigationTitle: "Colors") {
ForEach(Gradient.compound.allValues, id: \.name) { gradient in
GradientItem(gradient: gradient.value, name: gradient.name)
}
}
}
}
struct GradientItem: View {
let gradient: Gradient
let name: String
var body: some View {
HStack {
swatch
VStack(alignment: .leading) {
Text(name)
.font(.compound.bodyLG)
.foregroundColor(.compound.textPrimary)
}
}
}
var swatch: some View {
swatchShape
.foregroundStyle(gradient)
.frame(height: 40)
.aspectRatio(1, contentMode: .fit)
.overlay {
swatchShape
.strokeBorder(Color.compound.iconPrimary, lineWidth: 1.5)
.opacity(0.2)
}
}
var swatchShape: some InsettableShape {
RoundedRectangle(cornerRadius: 12, style: .continuous)
}
}
struct GradientsScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ColorsScreen()
}
.previewLayout(.fixed(width: 375, height: 700))
}
}
@@ -0,0 +1,47 @@
//
// Copyright 2023, 2024 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
import Compound
struct IconsScreen: View {
let icons = Image.compound.allValues.sorted(by: { $0.name < $1.name })
var body: some View {
ScreenContent(navigationTitle: "Icons") {
ForEach(icons, id: \.name) { icon in
IconItem(icon: icon.value, name: icon.name)
}
}
}
}
struct IconItem: View {
let icon: Image
let name: String
var body: some View {
Label {
Text(name)
.foregroundColor(.compound.textPrimary)
} icon: {
CompoundIcon(customImage: icon)
.foregroundColor(.compound.iconSecondary)
}
.font(.compound.bodyLG)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
struct IconsScreen_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
IconsScreen()
}
.previewLayout(.fixed(width: 375, height: 750))
}
}