mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
Merge pull request #7 from phranck/feature/container-config
refactor: Extract ContainerConfig and renderContainer helper (B.3)
This commit is contained in:
@@ -42,17 +42,8 @@ public struct Alert<Actions: View>: View {
|
||||
/// The alert message.
|
||||
public let message: String
|
||||
|
||||
/// The border style for the alert box (nil uses appearance default).
|
||||
public let borderStyle: BorderStyle?
|
||||
|
||||
/// The border color.
|
||||
public let borderColor: Color?
|
||||
|
||||
/// The title color.
|
||||
public let titleColor: Color?
|
||||
|
||||
/// Whether to show a separator before the action buttons.
|
||||
public let showFooterSeparator: Bool
|
||||
/// The shared visual configuration.
|
||||
public let config: ContainerConfig
|
||||
|
||||
/// The action views (typically buttons).
|
||||
public let actions: Actions
|
||||
@@ -78,10 +69,13 @@ public struct Alert<Actions: View>: View {
|
||||
) {
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.showFooterSeparator = showFooterSeparator
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: titleColor,
|
||||
padding: EdgeInsets(horizontal: 2, vertical: 1),
|
||||
showFooterSeparator: showFooterSeparator
|
||||
)
|
||||
self.actions = actions()
|
||||
}
|
||||
|
||||
@@ -94,44 +88,24 @@ public struct Alert<Actions: View>: View {
|
||||
|
||||
extension Alert: Renderable {
|
||||
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
|
||||
let containerStyle = ContainerStyle(
|
||||
showHeaderSeparator: true,
|
||||
showFooterSeparator: showFooterSeparator,
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor
|
||||
)
|
||||
|
||||
// Check if actions is EmptyView (no actions)
|
||||
let hasActions = !(actions is EmptyView)
|
||||
|
||||
if hasActions {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: titleColor,
|
||||
style: containerStyle,
|
||||
padding: EdgeInsets(horizontal: 2, vertical: 1)
|
||||
) {
|
||||
Text(message)
|
||||
} footer: {
|
||||
actions
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
} else {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: titleColor,
|
||||
style: ContainerStyle(
|
||||
showHeaderSeparator: true,
|
||||
showFooterSeparator: false,
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor
|
||||
),
|
||||
padding: EdgeInsets(horizontal: 2, vertical: 1)
|
||||
) {
|
||||
Text(message)
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
}
|
||||
let effectiveConfig = hasActions
|
||||
? config
|
||||
: ContainerConfig(
|
||||
borderStyle: config.borderStyle,
|
||||
borderColor: config.borderColor,
|
||||
titleColor: config.titleColor,
|
||||
padding: config.padding,
|
||||
showFooterSeparator: false
|
||||
)
|
||||
|
||||
return renderContainer(
|
||||
title: title,
|
||||
config: effectiveConfig,
|
||||
content: Text(message),
|
||||
footer: hasActions ? actions : nil,
|
||||
context: context
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,10 +129,13 @@ extension Alert where Actions == EmptyView {
|
||||
) {
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.showFooterSeparator = false
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: titleColor,
|
||||
padding: EdgeInsets(horizontal: 2, vertical: 1),
|
||||
showFooterSeparator: false
|
||||
)
|
||||
self.actions = EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,24 +52,12 @@ public struct Card<Content: View, Footer: View>: View {
|
||||
/// The footer content (optional).
|
||||
public let footer: Footer?
|
||||
|
||||
/// The border style (nil uses appearance default).
|
||||
public let borderStyle: BorderStyle?
|
||||
|
||||
/// The border color.
|
||||
public let borderColor: Color?
|
||||
|
||||
/// The title color.
|
||||
public let titleColor: Color?
|
||||
/// The shared visual configuration.
|
||||
public let config: ContainerConfig
|
||||
|
||||
/// The background color (nil for transparent).
|
||||
public let backgroundColor: Color?
|
||||
|
||||
/// The padding inside the card.
|
||||
public let padding: EdgeInsets
|
||||
|
||||
/// Whether to show a separator before the footer.
|
||||
public let showFooterSeparator: Bool
|
||||
|
||||
/// Creates a card with all options including footer.
|
||||
///
|
||||
/// - Parameters:
|
||||
@@ -96,12 +84,14 @@ public struct Card<Content: View, Footer: View>: View {
|
||||
self.title = title
|
||||
self.content = content()
|
||||
self.footer = footer()
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: titleColor,
|
||||
padding: padding,
|
||||
showFooterSeparator: showFooterSeparator
|
||||
)
|
||||
self.backgroundColor = backgroundColor
|
||||
self.padding = padding
|
||||
self.showFooterSeparator = showFooterSeparator
|
||||
}
|
||||
|
||||
public var body: Never {
|
||||
@@ -113,13 +103,6 @@ public struct Card<Content: View, Footer: View>: View {
|
||||
|
||||
extension Card: Renderable {
|
||||
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
|
||||
let containerStyle = ContainerStyle(
|
||||
showHeaderSeparator: true,
|
||||
showFooterSeparator: showFooterSeparator,
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor
|
||||
)
|
||||
|
||||
// Wrap content with background if specified
|
||||
let bodyContent: AnyView
|
||||
if let bgColor = backgroundColor {
|
||||
@@ -127,30 +110,14 @@ extension Card: Renderable {
|
||||
} else {
|
||||
bodyContent = AnyView(content)
|
||||
}
|
||||
|
||||
if let footerView = footer {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: titleColor,
|
||||
style: containerStyle,
|
||||
padding: padding
|
||||
) {
|
||||
bodyContent
|
||||
} footer: {
|
||||
footerView
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
} else {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: titleColor,
|
||||
style: containerStyle,
|
||||
padding: padding
|
||||
) {
|
||||
bodyContent
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
}
|
||||
|
||||
return renderContainer(
|
||||
title: title,
|
||||
config: config,
|
||||
content: bodyContent,
|
||||
footer: footer,
|
||||
context: context
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,12 +146,14 @@ extension Card where Footer == EmptyView {
|
||||
self.title = title
|
||||
self.content = content()
|
||||
self.footer = nil
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: titleColor,
|
||||
padding: padding,
|
||||
showFooterSeparator: false
|
||||
)
|
||||
self.backgroundColor = backgroundColor
|
||||
self.padding = padding
|
||||
self.showFooterSeparator = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,11 +180,13 @@ extension Card where Footer == EmptyView {
|
||||
self.title = nil
|
||||
self.content = content()
|
||||
self.footer = nil
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = nil
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: nil,
|
||||
padding: padding,
|
||||
showFooterSeparator: false
|
||||
)
|
||||
self.backgroundColor = backgroundColor
|
||||
self.padding = padding
|
||||
self.showFooterSeparator = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,65 @@
|
||||
// A unified container component with Header/Body/Footer architecture.
|
||||
//
|
||||
|
||||
// MARK: - Container Config
|
||||
|
||||
/// Shared visual configuration for container-type views.
|
||||
///
|
||||
/// Groups the common appearance properties used by ``Alert``, ``Dialog``,
|
||||
/// ``Panel``, and ``Card``. Each view stores a `ContainerConfig` instead
|
||||
/// of repeating the same five properties.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```swift
|
||||
/// let config = ContainerConfig(
|
||||
/// borderStyle: .doubleLine,
|
||||
/// borderColor: .cyan,
|
||||
/// titleColor: .cyan
|
||||
/// )
|
||||
/// ```
|
||||
public struct ContainerConfig: Sendable {
|
||||
/// The border style (nil uses appearance default).
|
||||
public var borderStyle: BorderStyle?
|
||||
|
||||
/// The border color (nil uses theme default).
|
||||
public var borderColor: Color?
|
||||
|
||||
/// The title color (nil uses theme accent).
|
||||
public var titleColor: Color?
|
||||
|
||||
/// The inner padding for the body content.
|
||||
public var padding: EdgeInsets
|
||||
|
||||
/// Whether to show a separator line between body and footer.
|
||||
public var showFooterSeparator: Bool
|
||||
|
||||
/// Creates a container configuration.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - borderStyle: The border style (default: appearance default).
|
||||
/// - borderColor: The border color (default: theme border).
|
||||
/// - titleColor: The title color (default: theme accent).
|
||||
/// - padding: The inner padding (default: horizontal 1, vertical 0).
|
||||
/// - showFooterSeparator: Show separator before footer (default: true).
|
||||
public init(
|
||||
borderStyle: BorderStyle? = nil,
|
||||
borderColor: Color? = nil,
|
||||
titleColor: Color? = nil,
|
||||
padding: EdgeInsets = EdgeInsets(horizontal: 1, vertical: 0),
|
||||
showFooterSeparator: Bool = true
|
||||
) {
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.padding = padding
|
||||
self.showFooterSeparator = showFooterSeparator
|
||||
}
|
||||
|
||||
/// Default configuration.
|
||||
public static let `default` = ContainerConfig()
|
||||
}
|
||||
|
||||
// MARK: - Container Style
|
||||
|
||||
/// Configuration options for container appearance.
|
||||
@@ -44,11 +103,74 @@ public struct ContainerStyle: Sendable {
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
}
|
||||
|
||||
|
||||
/// Creates a `ContainerStyle` from a ``ContainerConfig``.
|
||||
///
|
||||
/// - Parameter config: The container configuration to use.
|
||||
public init(from config: ContainerConfig) {
|
||||
self.showHeaderSeparator = true
|
||||
self.showFooterSeparator = config.showFooterSeparator
|
||||
self.borderStyle = config.borderStyle
|
||||
self.borderColor = config.borderColor
|
||||
}
|
||||
|
||||
/// Default container style.
|
||||
public static let `default` = ContainerStyle()
|
||||
}
|
||||
|
||||
// MARK: - Render Helper
|
||||
|
||||
/// Renders a ``ContainerView`` from a ``ContainerConfig`` and content/footer views.
|
||||
///
|
||||
/// Eliminates the duplicated `if/else` footer pattern found in Alert, Dialog,
|
||||
/// Panel, and Card.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - title: The container title (optional).
|
||||
/// - config: The shared visual configuration.
|
||||
/// - content: The body content view.
|
||||
/// - footer: The footer view (optional).
|
||||
/// - context: The current render context.
|
||||
/// - Returns: The rendered frame buffer.
|
||||
internal func renderContainer<Content: View, Footer: View>(
|
||||
title: String?,
|
||||
config: ContainerConfig,
|
||||
content: Content,
|
||||
footer: Footer?,
|
||||
context: RenderContext
|
||||
) -> FrameBuffer {
|
||||
let containerStyle = ContainerStyle(from: config)
|
||||
|
||||
if let footerView = footer {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: config.titleColor,
|
||||
style: containerStyle,
|
||||
padding: config.padding
|
||||
) {
|
||||
content
|
||||
} footer: {
|
||||
footerView
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
} else {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: config.titleColor,
|
||||
style: ContainerStyle(
|
||||
showHeaderSeparator: true,
|
||||
showFooterSeparator: false,
|
||||
borderStyle: config.borderStyle,
|
||||
borderColor: config.borderColor
|
||||
),
|
||||
padding: config.padding
|
||||
) {
|
||||
content
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Container View
|
||||
|
||||
/// A unified container with optional header, body, and footer sections.
|
||||
|
||||
@@ -60,20 +60,8 @@ public struct Dialog<Content: View, Footer: View>: View {
|
||||
/// The footer content (typically buttons).
|
||||
public let footer: Footer?
|
||||
|
||||
/// The border style (nil uses appearance default).
|
||||
public let borderStyle: BorderStyle?
|
||||
|
||||
/// The border color.
|
||||
public let borderColor: Color?
|
||||
|
||||
/// The title color.
|
||||
public let titleColor: Color?
|
||||
|
||||
/// The inner padding.
|
||||
public let padding: EdgeInsets
|
||||
|
||||
/// Whether to show a separator before the footer.
|
||||
public let showFooterSeparator: Bool
|
||||
/// The shared visual configuration.
|
||||
public let config: ContainerConfig
|
||||
|
||||
/// Creates a dialog with content and footer.
|
||||
///
|
||||
@@ -97,11 +85,13 @@ public struct Dialog<Content: View, Footer: View>: View {
|
||||
@ViewBuilder footer: () -> Footer
|
||||
) {
|
||||
self.title = title
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.padding = padding
|
||||
self.showFooterSeparator = showFooterSeparator
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: titleColor,
|
||||
padding: padding,
|
||||
showFooterSeparator: showFooterSeparator
|
||||
)
|
||||
self.content = content()
|
||||
self.footer = footer()
|
||||
}
|
||||
@@ -115,36 +105,13 @@ public struct Dialog<Content: View, Footer: View>: View {
|
||||
|
||||
extension Dialog: Renderable {
|
||||
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
|
||||
let containerStyle = ContainerStyle(
|
||||
showHeaderSeparator: true,
|
||||
showFooterSeparator: showFooterSeparator,
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor
|
||||
renderContainer(
|
||||
title: title,
|
||||
config: config,
|
||||
content: content,
|
||||
footer: footer,
|
||||
context: context
|
||||
)
|
||||
|
||||
if let footerView = footer {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: titleColor,
|
||||
style: containerStyle,
|
||||
padding: padding
|
||||
) {
|
||||
content
|
||||
} footer: {
|
||||
footerView
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
} else {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: titleColor,
|
||||
style: containerStyle,
|
||||
padding: padding
|
||||
) {
|
||||
content
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,11 +136,13 @@ extension Dialog where Footer == EmptyView {
|
||||
@ViewBuilder content: () -> Content
|
||||
) {
|
||||
self.title = title
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.padding = padding
|
||||
self.showFooterSeparator = false
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: titleColor,
|
||||
padding: padding,
|
||||
showFooterSeparator: false
|
||||
)
|
||||
self.content = content()
|
||||
self.footer = nil
|
||||
}
|
||||
@@ -228,5 +197,3 @@ extension Dialog where Footer == EmptyView {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -53,20 +53,8 @@ public struct Panel<Content: View, Footer: View>: View {
|
||||
/// The footer content (typically buttons).
|
||||
public let footer: Footer?
|
||||
|
||||
/// The border style (nil uses appearance default).
|
||||
public let borderStyle: BorderStyle?
|
||||
|
||||
/// The border color.
|
||||
public let borderColor: Color?
|
||||
|
||||
/// The title color.
|
||||
public let titleColor: Color?
|
||||
|
||||
/// The padding inside the panel body.
|
||||
public let padding: EdgeInsets
|
||||
|
||||
/// Whether to show a separator before the footer.
|
||||
public let showFooterSeparator: Bool
|
||||
/// The shared visual configuration.
|
||||
public let config: ContainerConfig
|
||||
|
||||
/// Creates a panel with content and footer.
|
||||
///
|
||||
@@ -92,11 +80,13 @@ public struct Panel<Content: View, Footer: View>: View {
|
||||
self.title = title
|
||||
self.content = content()
|
||||
self.footer = footer()
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.padding = padding
|
||||
self.showFooterSeparator = showFooterSeparator
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: titleColor,
|
||||
padding: padding,
|
||||
showFooterSeparator: showFooterSeparator
|
||||
)
|
||||
}
|
||||
|
||||
public var body: Never {
|
||||
@@ -127,11 +117,13 @@ extension Panel where Footer == EmptyView {
|
||||
self.title = title
|
||||
self.content = content()
|
||||
self.footer = nil
|
||||
self.borderStyle = borderStyle
|
||||
self.borderColor = borderColor
|
||||
self.titleColor = titleColor
|
||||
self.padding = padding
|
||||
self.showFooterSeparator = true
|
||||
self.config = ContainerConfig(
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor,
|
||||
titleColor: titleColor,
|
||||
padding: padding,
|
||||
showFooterSeparator: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,36 +131,12 @@ extension Panel where Footer == EmptyView {
|
||||
|
||||
extension Panel: Renderable {
|
||||
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
|
||||
// Create the ContainerView and render it
|
||||
let containerStyle = ContainerStyle(
|
||||
showHeaderSeparator: true,
|
||||
showFooterSeparator: showFooterSeparator,
|
||||
borderStyle: borderStyle,
|
||||
borderColor: borderColor
|
||||
renderContainer(
|
||||
title: title,
|
||||
config: config,
|
||||
content: content,
|
||||
footer: footer,
|
||||
context: context
|
||||
)
|
||||
|
||||
if let footerView = footer {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: titleColor,
|
||||
style: containerStyle,
|
||||
padding: padding
|
||||
) {
|
||||
content
|
||||
} footer: {
|
||||
footerView
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
} else {
|
||||
let container = ContainerView(
|
||||
title: title,
|
||||
titleColor: titleColor,
|
||||
style: containerStyle,
|
||||
padding: padding
|
||||
) {
|
||||
content
|
||||
}
|
||||
return container.renderToBuffer(context: context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user