Skip to content

Instantly share code, notes, and snippets.

@jacobsapps
Created January 8, 2026 15:31
Show Gist options
  • Select an option

  • Save jacobsapps/d012397607ce1c9608091ea64cdd7347 to your computer and use it in GitHub Desktop.

Select an option

Save jacobsapps/d012397607ce1c9608091ea64cdd7347 to your computer and use it in GitHub Desktop.
//
// DisclosureButton.swift
// DesignSystemWar
//
// Created by Jacob Bartlett on 07/05/2023.
//
import SwiftUI
/// Button component that exposes variants and options via initializer defaults.
struct DisclosureButton: View {
// MARK: - Properties -
private let variant: ButtonVariant
private let color: ButtonColor
private let size: ButtonSize
private let icon: ButtonIcon?
private let label: AnyView
private let action: () -> Void
@Environment(\.isEnabled) private var isEnabled
// MARK: - Init -
/// Creates a design-system button with progressive disclosure defaults.
/// - Parameters:
/// - variant: Visual style for fill, border, or text-only.
/// - color: Semantic color for the button surface.
/// - size: Height and icon scale for the button.
/// - icon: Optional leading or trailing icon.
/// - title: Button title text.
/// - action: Action performed on tap.
init(variant: ButtonVariant = .filled,
color: ButtonColor = .default,
size: ButtonSize = .large,
icon: ButtonIcon? = nil,
title: String,
action: @escaping () -> Void) {
self.variant = variant
self.color = color
self.size = size
self.icon = icon
self.label = AnyView(Text(title))
self.action = action
}
/// Creates a design-system button with a custom label view.
/// - Parameters:
/// - variant: Visual style for fill, border, or text-only.
/// - color: Semantic color for the button surface.
/// - size: Height and icon scale for the button.
/// - icon: Optional leading or trailing icon.
/// - label: Custom view used as the button label.
/// - action: Action performed on tap.
init(variant: ButtonVariant = .filled,
color: ButtonColor = .default,
size: ButtonSize = .large,
icon: ButtonIcon? = nil,
@ViewBuilder label: () -> some View,
action: @escaping () -> Void) {
self.variant = variant
self.color = color
self.size = size
self.icon = icon
self.label = AnyView(label())
self.action = action
}
// MARK: - Body -
var body: some View {
Button(action: action, label: {
buttonForVariant
})
.opacity(isEnabled ? 1 : 0.5)
}
@ViewBuilder
private var buttonForVariant: some View {
switch variant {
case .filled:
filledButton
case .bordered:
borderedButton
case .text:
textButton
}
}
private var textButton: some View {
buttonContent
.foregroundColor(color.mainColor)
.frame(height: size.height)
.contentShape(Rectangle())
}
private var borderedButton: some View {
buttonContent
.foregroundColor(color.mainColor)
.frame(height: size.height)
.buttonBorder(borderColor: color.mainColor, size: size)
}
private var filledButton: some View {
buttonContent
.foregroundColor(color.textColor)
.frame(height: size.height)
.buttonBackground(fillColor: color.mainColor)
}
private var buttonContent: some View {
ButtonLabelContent(label: label, size: size, icon: icon)
}
}
#Preview("Disclosure Filled") {
let sizes: [ButtonSize] = [.large, .small]
let colors = ButtonColor.standardColors
VStack(spacing: 12) {
ForEach(Array(colors.enumerated()), id: \.offset) { _, color in
ForEach(Array(sizes.enumerated()), id: \.offset) { _, size in
DisclosureButton(variant: .filled,
color: color,
size: size,
icon: .leading(.AppIcon.star),
title: "\(color.label) \(size.label)",
action: {})
}
}
}
.padding()
.background(Color(.secondarySystemBackground))
}
#Preview("Disclosure Bordered") {
let sizes: [ButtonSize] = [.large, .small]
let colors = ButtonColor.standardColors
VStack(spacing: 12) {
ForEach(Array(colors.enumerated()), id: \.offset) { _, color in
ForEach(Array(sizes.enumerated()), id: \.offset) { _, size in
DisclosureButton(variant: .bordered,
color: color,
size: size,
icon: .trailing(.AppIcon.bookmark),
title: "\(color.label) \(size.label)",
action: {})
}
}
}
.padding()
.background(Color(.secondarySystemBackground))
}
#Preview("Disclosure Text") {
let sizes: [ButtonSize] = [.large, .small]
let colors = ButtonColor.standardColors
VStack(spacing: 12) {
ForEach(Array(colors.enumerated()), id: \.offset) { _, color in
ForEach(Array(sizes.enumerated()), id: \.offset) { _, size in
DisclosureButton(variant: .text,
color: color,
size: size,
icon: .leading(.AppIcon.flame),
title: "\(color.label) \(size.label)",
action: {})
}
}
}
.padding()
.background(Color(.secondarySystemBackground))
}
#Preview("Disclosure Custom Label") {
DisclosureButton(variant: .bordered,
color: .accent,
size: .large,
icon: .leading(.AppIcon.sparkles),
label: {
HStack(spacing: 6) {
Text("Custom")
Text("Label")
.bold()
}
},
action: {})
.padding()
.background(Color(.secondarySystemBackground))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment