239 lines
7.6 KiB
Swift
Executable file
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
|