feat(StatusBar): Add appearance system integration

- StatusBar bordered style now uses appearance-based borderStyle
- Replace help item (`?`) with appearance item (`a`) in system items
- Add `showAppearanceItem` flag to StatusBarState
- Add `StatusBarItemOrder.appearance` for proper ordering
- Update SystemStatusBarItem to include appearance cycling
- Pressing `a` now cycles through appearances (line, rounded, doubleLine, heavy, block)

The StatusBar bordered style adapts to the current appearance, showing the
same border style as all other controls in the app.
This commit is contained in:
phranck
2026-01-28 22:47:33 +01:00
parent 6c816a1770
commit fb434fe77d
3 changed files with 47 additions and 43 deletions
+6 -6
View File
@@ -107,11 +107,11 @@ public final class StatusBarState: @unchecked Sendable {
/// Default is `true`.
public var showSystemItems: Bool = true
/// Whether the help item (`?`) is shown.
/// Whether the appearance item (`a`) is shown.
///
/// When `true`, pressing `?` shows available shortcuts.
/// When `true`, pressing `a` cycles through available appearances (border styles).
/// Default is `true`.
public var showHelpItem: Bool = true
public var showAppearanceItem: Bool = true
/// Whether the theme item (`t`) is shown.
///
@@ -166,7 +166,7 @@ public final class StatusBarState: @unchecked Sendable {
/// The current system items based on configuration flags.
///
/// Returns items filtered by `showSystemItems`, `showHelpItem`, `showThemeItem`,
/// Returns items filtered by `showSystemItems`, `showAppearanceItem`, `showThemeItem`,
/// and `quitBehavior`. The quit item is only included when quit is allowed.
public var currentSystemItems: [StatusBarItem] {
guard showSystemItems else { return [] }
@@ -178,8 +178,8 @@ public final class StatusBarState: @unchecked Sendable {
items.append(SystemStatusBarItem.quit)
}
if showHelpItem {
items.append(SystemStatusBarItem.help)
if showAppearanceItem {
items.append(SystemStatusBarItem.appearance)
}
if showThemeItem {
+26 -24
View File
@@ -286,11 +286,11 @@ public struct StatusBarItemOrder: Comparable, Sendable {
// MARK: - System Item Orders (right side)
/// Order for the quit item (leftmost of system items).
/// Appears as: `[...user items] [q quit] [? help] [t theme]`
/// Appears as: `[...user items] [q quit] [a appearance] [t theme]`
public static let quit = StatusBarItemOrder(900)
/// Order for the help item (middle system item).
public static let help = StatusBarItemOrder(910)
/// Order for the appearance item (middle system item).
public static let appearance = StatusBarItemOrder(910)
/// Order for the theme item (rightmost).
public static let theme = StatusBarItemOrder(920)
@@ -477,8 +477,8 @@ public struct StatusBarItem: StatusBarItemProtocol, Identifiable {
///
/// System items include:
/// - **quit** (`q`): Exits the application
/// - **help** (`?`): Shows help (not yet implemented)
/// - **theme** (`t`): Cycles through themes (not yet implemented)
/// - **appearance** (`a`): Cycles through appearances
/// - **theme** (`t`): Cycles through themes
public enum SystemStatusBarItem {
/// The quit item (`q quit`).
///
@@ -489,13 +489,14 @@ public enum SystemStatusBarItem {
order: .quit
)
/// The help item (`? help`).
/// The appearance item (`a appearance`).
///
/// Shows application help. Action must be set by the framework.
public static let help = StatusBarItem(
shortcut: "?",
label: "help",
order: .help
/// Cycles through available appearances (border styles).
/// Action must be set by the framework.
public static let appearance = StatusBarItem(
shortcut: "a",
label: "appearance",
order: .appearance
)
/// The theme item (`t theme`).
@@ -509,19 +510,19 @@ public enum SystemStatusBarItem {
/// All system items in their default order.
public static var all: [StatusBarItem] {
[quit, help, theme]
[quit, appearance, theme]
}
/// Creates system items with custom actions.
///
/// - Parameters:
/// - onQuit: Action for quit (default: exits app).
/// - onHelp: Action for help (optional).
/// - onAppearance: Action for appearance cycling (optional).
/// - onTheme: Action for theme cycling (optional).
/// - Returns: Array of configured system items.
public static func items(
onQuit: (@Sendable () -> Void)? = nil,
onHelp: (@Sendable () -> Void)? = nil,
onAppearance: (@Sendable () -> Void)? = nil,
onTheme: (@Sendable () -> Void)? = nil
) -> [StatusBarItem] {
var result: [StatusBarItem] = []
@@ -534,13 +535,13 @@ public enum SystemStatusBarItem {
action: onQuit
))
// Help is present if action is provided
if let onHelp = onHelp {
// Appearance is present if action is provided
if let onAppearance = onAppearance {
result.append(StatusBarItem(
shortcut: "?",
label: "help",
order: .help,
action: onHelp
shortcut: "a",
label: "appearance",
order: .appearance,
action: onAppearance
))
}
@@ -787,7 +788,7 @@ extension StatusBar: Renderable {
return renderCompact(itemStrings: itemStrings, width: context.availableWidth)
case .bordered:
return renderBordered(itemStrings: itemStrings, width: context.availableWidth)
return renderBordered(itemStrings: itemStrings, width: context.availableWidth, context: context)
}
}
@@ -893,9 +894,10 @@ extension StatusBar: Renderable {
return FrameBuffer(lines: [line])
}
/// Renders the bordered style (block border with alignment).
private func renderBordered(itemStrings: [String], width: Int) -> FrameBuffer {
let border = BorderStyle.block
/// Renders the bordered style using the current appearance's border style.
private func renderBordered(itemStrings: [String], width: Int, context: RenderContext) -> FrameBuffer {
// Use the current appearance's border style
let border = context.environment.appearance.borderStyle
let innerWidth = width - 2 // Account for left and right border
let content = alignContent(itemStrings: itemStrings, width: innerWidth)
+15 -13
View File
@@ -268,11 +268,11 @@ struct StatusBarStateTests {
StatusBarItem(shortcut: "x", label: "extra")
])
// User items (s, x) + system items (q, ?, t) = 5 total
// User items (s, x) + system items (q, a, t) = 5 total
#expect(state.currentItems.count == 5)
#expect(state.hasItems == true)
#expect(state.currentItems.contains { $0.shortcut == "q" }) // system quit
#expect(state.currentItems.contains { $0.shortcut == "?" }) // system help
#expect(state.currentItems.contains { $0.shortcut == "a" }) // system appearance
#expect(state.currentItems.contains { $0.shortcut == "t" }) // system theme
#expect(state.currentItems.contains { $0.shortcut == "s" }) // user save
#expect(state.currentItems.contains { $0.shortcut == "x" }) // user extra
@@ -287,7 +287,7 @@ struct StatusBarStateTests {
StatusBarItem(shortcut: "x", label: "extra")
}
// User items (s, x) + system items (q, ?, t) = 5 total
// User items (s, x) + system items (q, a, t) = 5 total
#expect(state.currentItems.count == 5)
}
@@ -304,10 +304,10 @@ struct StatusBarStateTests {
StatusBarItem(shortcut: Shortcut.enter, label: "confirm")
])
// Context items (escape, enter) + system items (q, ?, t) = 5 total
// Context items (escape, enter) + system items (q, a, t) = 5 total
#expect(state.currentItems.count == 5)
#expect(state.currentItems.contains { $0.shortcut == "q" }) // system quit
#expect(state.currentItems.contains { $0.shortcut == "?" }) // system help
#expect(state.currentItems.contains { $0.shortcut == "a" }) // system appearance
#expect(state.currentItems.contains { $0.shortcut == "t" }) // system theme
#expect(state.currentItems.contains { $0.shortcut == Shortcut.escape })
#expect(state.currentItems.contains { $0.shortcut == Shortcut.enter })
@@ -318,14 +318,14 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.push(context: "test") {
StatusBarItem(shortcut: "a", label: "action")
StatusBarItem(shortcut: "x", label: "action") // Use 'x' to not conflict with 'a' (appearance)
}
// Context item (a) + system items (q, ?, t) = 4 total
// Context item (x) + system items (q, a, t) = 4 total
#expect(state.currentItems.count == 4)
#expect(state.currentItems.contains { $0.label == "action" })
#expect(state.currentItems.contains { $0.shortcut == "q" })
#expect(state.currentItems.contains { $0.shortcut == "?" })
#expect(state.currentItems.contains { $0.shortcut == "a" })
#expect(state.currentItems.contains { $0.shortcut == "t" })
}
@@ -343,11 +343,11 @@ struct StatusBarStateTests {
state.pop(context: "temp")
// Global item (g) + system items (q, ?, t) = 4 total
// Global item (g) + system items (q, a, t) = 4 total
#expect(state.currentItems.count == 4)
#expect(state.currentItems.contains { $0.shortcut == "g" })
#expect(state.currentItems.contains { $0.shortcut == "q" })
#expect(state.currentItems.contains { $0.shortcut == "?" })
#expect(state.currentItems.contains { $0.shortcut == "a" })
#expect(state.currentItems.contains { $0.shortcut == "t" })
}
@@ -616,13 +616,15 @@ struct StatusBarTests {
StatusBarItem(shortcut: "h", label: "help")
], style: .bordered)
// Use default appearance (rounded)
let context = RenderContext(availableWidth: 80, availableHeight: 24)
let buffer = renderToBuffer(statusBar, context: context)
#expect(buffer.height == 3)
// Should have block border characters
// Should have border characters (appearance-based, default is rounded: )
let allContent = buffer.lines.joined()
#expect(allContent.contains("") || allContent.contains("") || allContent.contains(""))
#expect(allContent.contains("") || allContent.contains("") || allContent.contains("") ||
allContent.contains("") || allContent.contains("") || allContent.contains(""))
}
@Test("Empty StatusBar returns empty buffer")
@@ -1073,7 +1075,7 @@ struct SystemStatusBarItemsTests {
func systemItemOrderConstants() {
// System items should have high order values (900+)
#expect(StatusBarItemOrder.quit.value == 900)
#expect(StatusBarItemOrder.help.value == 910)
#expect(StatusBarItemOrder.appearance.value == 910)
#expect(StatusBarItemOrder.theme.value == 920)
// User items should have lower order values