393 lines
13 KiB
Swift
Executable file
393 lines
13 KiB
Swift
Executable file
// Section.swift
|
|
// iOS UI Components - Layout Components
|
|
//
|
|
// Grouped content with optional header and footer
|
|
|
|
import SwiftUI
|
|
import LilithDesignTokens
|
|
|
|
/// Section container
|
|
///
|
|
/// Grouped content with optional header, footer, and background.
|
|
///
|
|
/// Example:
|
|
/// ```swift
|
|
/// Section(header: "Settings") {
|
|
/// Text("Option 1")
|
|
/// Text("Option 2")
|
|
/// }
|
|
/// ```
|
|
public struct Section<Content: View, Header: View, Footer: View>: View {
|
|
// MARK: - Properties
|
|
|
|
private let header: Header?
|
|
private let footer: Footer?
|
|
private let content: Content
|
|
private let spacing: CGFloat
|
|
private let padding: CGFloat
|
|
private let backgroundColor: Color?
|
|
|
|
// MARK: - Initialization
|
|
|
|
/// Create a section with custom header and footer
|
|
/// - Parameters:
|
|
/// - spacing: Spacing between items (default: sm)
|
|
/// - padding: Inner padding (default: base)
|
|
/// - backgroundColor: Optional background color
|
|
/// - header: Header view
|
|
/// - footer: Footer view
|
|
/// - content: Section content
|
|
public init(
|
|
spacing: CGFloat = AppSpacing.sm,
|
|
padding: CGFloat = AppSpacing.base,
|
|
backgroundColor: Color? = nil,
|
|
@ViewBuilder header: () -> Header,
|
|
@ViewBuilder footer: () -> Footer,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.backgroundColor = backgroundColor
|
|
self.header = header()
|
|
self.footer = footer()
|
|
self.content = content()
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
public var body: some View {
|
|
VStack(alignment: .leading, spacing: AppSpacing.sm) {
|
|
// Header
|
|
if let header = header {
|
|
header
|
|
}
|
|
|
|
// Content
|
|
VStack(alignment: .leading, spacing: spacing) {
|
|
content
|
|
}
|
|
.padding(padding)
|
|
.background(backgroundColor)
|
|
.clipShape(RoundedRectangle(cornerRadius: backgroundColor != nil ? AppRadius.card : 0))
|
|
|
|
// Footer
|
|
if let footer = footer {
|
|
footer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Convenience Initializers
|
|
|
|
extension Section where Header == Text {
|
|
/// Create a section with text header
|
|
/// - Parameters:
|
|
/// - header: Header text
|
|
/// - spacing: Spacing between items
|
|
/// - padding: Inner padding
|
|
/// - backgroundColor: Optional background color
|
|
/// - footer: Footer view
|
|
/// - content: Section content
|
|
public init(
|
|
header: String,
|
|
spacing: CGFloat = AppSpacing.sm,
|
|
padding: CGFloat = AppSpacing.base,
|
|
backgroundColor: Color? = AppColors.surface,
|
|
@ViewBuilder footer: () -> Footer,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.backgroundColor = backgroundColor
|
|
self.header = Text(header)
|
|
.font(AppTypography.label(weight: .semibold))
|
|
.foregroundColor(AppColors.textSecondary)
|
|
self.footer = footer()
|
|
self.content = content()
|
|
}
|
|
}
|
|
|
|
extension Section where Footer == Text {
|
|
/// Create a section with text footer
|
|
/// - Parameters:
|
|
/// - footer: Footer text
|
|
/// - spacing: Spacing between items
|
|
/// - padding: Inner padding
|
|
/// - backgroundColor: Optional background color
|
|
/// - header: Header view
|
|
/// - content: Section content
|
|
public init(
|
|
footer: String,
|
|
spacing: CGFloat = AppSpacing.sm,
|
|
padding: CGFloat = AppSpacing.base,
|
|
backgroundColor: Color? = AppColors.surface,
|
|
@ViewBuilder header: () -> Header,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.backgroundColor = backgroundColor
|
|
self.header = header()
|
|
self.footer = Text(footer)
|
|
.font(AppTypography.caption())
|
|
.foregroundColor(AppColors.textSecondary)
|
|
self.content = content()
|
|
}
|
|
}
|
|
|
|
extension Section where Header == Text, Footer == Text {
|
|
/// Create a section with text header and footer
|
|
/// - Parameters:
|
|
/// - header: Header text
|
|
/// - footer: Footer text
|
|
/// - spacing: Spacing between items
|
|
/// - padding: Inner padding
|
|
/// - backgroundColor: Optional background color
|
|
/// - content: Section content
|
|
public init(
|
|
header: String,
|
|
footer: String,
|
|
spacing: CGFloat = AppSpacing.sm,
|
|
padding: CGFloat = AppSpacing.base,
|
|
backgroundColor: Color? = AppColors.surface,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.backgroundColor = backgroundColor
|
|
self.header = Text(header)
|
|
.font(AppTypography.label(weight: .semibold))
|
|
.foregroundColor(AppColors.textSecondary)
|
|
self.footer = Text(footer)
|
|
.font(AppTypography.caption())
|
|
.foregroundColor(AppColors.textSecondary)
|
|
self.content = content()
|
|
}
|
|
}
|
|
|
|
extension Section where Header == EmptyView {
|
|
/// Create a section without header
|
|
/// - Parameters:
|
|
/// - spacing: Spacing between items
|
|
/// - padding: Inner padding
|
|
/// - backgroundColor: Optional background color
|
|
/// - footer: Footer view
|
|
/// - content: Section content
|
|
public init(
|
|
spacing: CGFloat = AppSpacing.sm,
|
|
padding: CGFloat = AppSpacing.base,
|
|
backgroundColor: Color? = AppColors.surface,
|
|
@ViewBuilder footer: () -> Footer,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.backgroundColor = backgroundColor
|
|
self.header = nil
|
|
self.footer = footer()
|
|
self.content = content()
|
|
}
|
|
}
|
|
|
|
extension Section where Footer == EmptyView {
|
|
/// Create a section without footer
|
|
/// - Parameters:
|
|
/// - spacing: Spacing between items
|
|
/// - padding: Inner padding
|
|
/// - backgroundColor: Optional background color
|
|
/// - header: Header view
|
|
/// - content: Section content
|
|
public init(
|
|
spacing: CGFloat = AppSpacing.sm,
|
|
padding: CGFloat = AppSpacing.base,
|
|
backgroundColor: Color? = AppColors.surface,
|
|
@ViewBuilder header: () -> Header,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.backgroundColor = backgroundColor
|
|
self.header = header()
|
|
self.footer = nil
|
|
self.content = content()
|
|
}
|
|
}
|
|
|
|
extension Section where Header == Text, Footer == EmptyView {
|
|
/// Create a section with only text header
|
|
/// - Parameters:
|
|
/// - header: Header text
|
|
/// - spacing: Spacing between items
|
|
/// - padding: Inner padding
|
|
/// - backgroundColor: Optional background color
|
|
/// - content: Section content
|
|
public init(
|
|
header: String,
|
|
spacing: CGFloat = AppSpacing.sm,
|
|
padding: CGFloat = AppSpacing.base,
|
|
backgroundColor: Color? = AppColors.surface,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.backgroundColor = backgroundColor
|
|
self.header = Text(header)
|
|
.font(AppTypography.label(weight: .semibold))
|
|
.foregroundColor(AppColors.textSecondary)
|
|
self.footer = nil
|
|
self.content = content()
|
|
}
|
|
}
|
|
|
|
extension Section where Header == EmptyView, Footer == EmptyView {
|
|
/// Create a section without header or footer
|
|
/// - Parameters:
|
|
/// - spacing: Spacing between items
|
|
/// - padding: Inner padding
|
|
/// - backgroundColor: Optional background color
|
|
/// - content: Section content
|
|
public init(
|
|
spacing: CGFloat = AppSpacing.sm,
|
|
padding: CGFloat = AppSpacing.base,
|
|
backgroundColor: Color? = AppColors.surface,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.backgroundColor = backgroundColor
|
|
self.header = nil
|
|
self.footer = nil
|
|
self.content = content()
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview Provider
|
|
|
|
#if DEBUG
|
|
struct Section_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
ScrollView {
|
|
VStack(spacing: AppSpacing.xl) {
|
|
// Section with header
|
|
Section(header: "Account") {
|
|
sectionRow("Email", value: "john@example.com")
|
|
sectionRow("Phone", value: "+1 (555) 123-4567")
|
|
sectionRow("Password", value: "••••••••")
|
|
}
|
|
|
|
// Section with header and footer
|
|
Section(
|
|
header: "Notifications",
|
|
footer: "Manage how you receive notifications from us"
|
|
) {
|
|
sectionRow("Push Notifications", value: "On")
|
|
sectionRow("Email Notifications", value: "On")
|
|
sectionRow("SMS Notifications", value: "Off")
|
|
}
|
|
|
|
// Section without background
|
|
Section(header: "Preferences", backgroundColor: nil) {
|
|
Text("Dark Mode")
|
|
Text("Language")
|
|
Text("Region")
|
|
}
|
|
|
|
// Section with custom header
|
|
Section {
|
|
HStack {
|
|
Image(systemName: "lock.shield")
|
|
.foregroundColor(AppColors.primary)
|
|
Text("SECURITY")
|
|
.font(AppTypography.label(weight: .semibold))
|
|
.foregroundColor(AppColors.textSecondary)
|
|
}
|
|
} footer: {
|
|
EmptyView()
|
|
} content: {
|
|
sectionRow("Two-Factor Auth", value: "Enabled")
|
|
sectionRow("Biometric Login", value: "Face ID")
|
|
sectionRow("Session Timeout", value: "30 minutes")
|
|
}
|
|
|
|
// In context - Settings screen
|
|
VStack(spacing: AppSpacing.xl) {
|
|
Section(header: "Profile") {
|
|
HStack {
|
|
Circle()
|
|
.fill(AppColors.Gray.gray600)
|
|
.frame(width: 60, height: 60)
|
|
.overlay(
|
|
Image(systemName: "person.fill")
|
|
.foregroundColor(AppColors.Gray.gray400)
|
|
)
|
|
|
|
VStack(alignment: .leading, spacing: AppSpacing.xs) {
|
|
Text("John Doe")
|
|
.font(AppTypography.body(weight: .semibold))
|
|
.foregroundColor(AppColors.textPrimary)
|
|
|
|
Text("View Profile")
|
|
.font(AppTypography.caption())
|
|
.foregroundColor(AppColors.primary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Image(systemName: "chevron.right")
|
|
.foregroundColor(AppColors.textTertiary)
|
|
}
|
|
}
|
|
|
|
Section(
|
|
header: "Appearance",
|
|
footer: "Customize the look and feel of the app"
|
|
) {
|
|
sectionRow("Theme", value: "Dark")
|
|
sectionRow("Accent Color", value: "Purple")
|
|
sectionRow("Text Size", value: "Medium")
|
|
}
|
|
|
|
Section(
|
|
header: "Privacy",
|
|
footer: "Control your privacy and data sharing preferences"
|
|
) {
|
|
sectionRow("Profile Visibility", value: "Public")
|
|
sectionRow("Activity Status", value: "Hidden")
|
|
sectionRow("Data Collection", value: "Minimal")
|
|
}
|
|
|
|
Section(header: "Support") {
|
|
sectionRow("Help Center", value: "")
|
|
sectionRow("Report a Problem", value: "")
|
|
sectionRow("App Version", value: "1.0.0")
|
|
}
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
.background(AppColors.background)
|
|
.previewDisplayName("Section States")
|
|
}
|
|
|
|
static func sectionRow(_ title: String, value: String) -> some View {
|
|
HStack {
|
|
Text(title)
|
|
.font(AppTypography.body())
|
|
.foregroundColor(AppColors.textPrimary)
|
|
|
|
Spacer()
|
|
|
|
if !value.isEmpty {
|
|
Text(value)
|
|
.font(AppTypography.body())
|
|
.foregroundColor(AppColors.textSecondary)
|
|
}
|
|
|
|
Image(systemName: "chevron.right")
|
|
.font(.system(size: 12))
|
|
.foregroundColor(AppColors.textTertiary)
|
|
}
|
|
}
|
|
}
|
|
#endif
|