swift-layout/Sources/LilithLayout/Container.swift
2026-02-16 09:23:07 -08:00

239 lines
7.6 KiB
Swift
Executable file

// Container.swift
// iOS UI Components - Layout Components
//
// Padded container with optional background
import SwiftUI
import LilithDesignTokens
/// Container component
///
/// Flexible container with padding and optional background.
///
/// Example:
/// ```swift
/// Container {
/// Text("Content")
/// }
/// ```
public struct Container<Content: View>: View {
// MARK: - Properties
private let content: Content
private let padding: EdgeInsets
private let backgroundColor: Color?
private let cornerRadius: CGFloat
private let border: (color: Color, width: CGFloat)?
// MARK: - Initialization
/// Create a container
/// - Parameters:
/// - padding: Edge insets (default: base all around)
/// - backgroundColor: Optional background color
/// - cornerRadius: Corner radius (default: 0)
/// - border: Optional border (color, width)
/// - content: Container content
public init(
padding: EdgeInsets = EdgeInsets(
top: AppSpacing.base,
leading: AppSpacing.base,
bottom: AppSpacing.base,
trailing: AppSpacing.base
),
backgroundColor: Color? = nil,
cornerRadius: CGFloat = 0,
border: (color: Color, width: CGFloat)? = nil,
@ViewBuilder content: () -> Content
) {
self.padding = padding
self.backgroundColor = backgroundColor
self.cornerRadius = cornerRadius
self.border = border
self.content = content()
}
/// Create a container with uniform padding
/// - Parameters:
/// - padding: Uniform padding value
/// - backgroundColor: Optional background color
/// - cornerRadius: Corner radius
/// - border: Optional border (color, width)
/// - content: Container content
public init(
padding: CGFloat = AppSpacing.base,
backgroundColor: Color? = nil,
cornerRadius: CGFloat = 0,
border: (color: Color, width: CGFloat)? = nil,
@ViewBuilder content: () -> Content
) {
self.padding = EdgeInsets(
top: padding,
leading: padding,
bottom: padding,
trailing: padding
)
self.backgroundColor = backgroundColor
self.cornerRadius = cornerRadius
self.border = border
self.content = content()
}
// MARK: - Body
public var body: some View {
content
.padding(padding)
.background(backgroundColor)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
.overlay(
border.map { border in
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(border.color, lineWidth: border.width)
}
)
}
}
// MARK: - Container Variants
extension Container {
/// Screen container with standard padding
public static func screen(@ViewBuilder content: () -> Content) -> Container<Content> {
Container(
padding: EdgeInsets(
top: AppSpacing.lg,
leading: AppSpacing.base,
bottom: AppSpacing.lg,
trailing: AppSpacing.base
),
content: content
)
}
/// Section container with vertical padding
public static func section(@ViewBuilder content: () -> Content) -> Container<Content> {
Container(
padding: EdgeInsets(
top: AppSpacing.md,
leading: AppSpacing.base,
bottom: AppSpacing.md,
trailing: AppSpacing.base
),
content: content
)
}
/// Component container with minimal padding
public static func component(@ViewBuilder content: () -> Content) -> Container<Content> {
Container(
padding: AppSpacing.componentPadding,
content: content
)
}
/// Card container with surface background
public static func card(@ViewBuilder content: () -> Content) -> Container<Content> {
Container(
padding: AppSpacing.base,
backgroundColor: AppColors.surface,
cornerRadius: AppRadius.card,
content: content
)
}
/// Bordered container
public static func bordered(
padding: CGFloat = AppSpacing.base,
borderColor: Color = AppColors.border,
borderWidth: CGFloat = 1,
cornerRadius: CGFloat = AppRadius.card,
@ViewBuilder content: () -> Content
) -> Container<Content> {
Container(
padding: padding,
cornerRadius: cornerRadius,
border: (color: borderColor, width: borderWidth),
content: content
)
}
/// Highlighted container with primary border
public static func highlighted(
padding: CGFloat = AppSpacing.base,
@ViewBuilder content: () -> Content
) -> Container<Content> {
Container(
padding: padding,
backgroundColor: AppColors.primary.opacity(0.05),
cornerRadius: AppRadius.card,
border: (color: AppColors.primary, width: 2),
content: content
)
}
}
// MARK: - Preview Provider
#if DEBUG
struct Container_Previews: PreviewProvider {
static var previews: some View {
ScrollView(.vertical, showsIndicators: true) {
VStack(spacing: AppSpacing.xl) {
// Basic container with explicit CGFloat padding
Container(padding: CGFloat(AppSpacing.base)) {
Text("Default container with base padding")
.font(AppTypography.body())
.foregroundColor(AppColors.textPrimary)
}
// Card container
Container.card {
Text("Card container with surface background")
.font(AppTypography.body())
.foregroundColor(AppColors.textPrimary)
}
// Bordered container
Container.bordered {
Text("Default bordered container")
.font(AppTypography.body())
.foregroundColor(AppColors.textPrimary)
}
// Highlighted container
Container.highlighted {
Text("Highlighted container")
.font(AppTypography.body())
.foregroundColor(AppColors.primary)
}
// Alert box
Container.bordered(
borderColor: AppColors.Semantic.warning,
borderWidth: 2
) {
HStack(spacing: AppSpacing.md) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(AppColors.Semantic.warning)
.font(.system(size: 24))
VStack(alignment: .leading, spacing: AppSpacing.xs) {
Text("Warning")
.font(AppTypography.body(weight: .semibold))
.foregroundColor(AppColors.textPrimary)
Text("This action cannot be undone")
.font(AppTypography.caption())
.foregroundColor(AppColors.textSecondary)
}
}
}
}
.padding()
}
.background(AppColors.background)
.previewDisplayName("Container States")
}
}
#endif