forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAppsSectionNavigation.swift
More file actions
90 lines (79 loc) · 2.92 KB
/
Copy pathAppsSectionNavigation.swift
File metadata and controls
90 lines (79 loc) · 2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//
// AppsSectionNavigation.swift — the Apps page's section chips.
//
// Apps carries two catalogs that are not apps: MCP servers and skills. They used to be stacked
// under Imports and Exports on one scroll, which left the page's search field and filters with no
// single subject.
//
// The chips follow Brain's rule (see `ActivityDestinationChip`): every chip here switches the
// page, none narrows a list. `Imports` and `Exports` therefore stay in the Kind menu beside them,
// which is a filter, rather than being promoted to chips they would not behave like.
//
import OmiTheme
import SwiftUI
/// One chip in the Apps row. Every case is a section; none is a filter.
enum AppsSectionDestination: Int, CaseIterable, Identifiable {
/// Raw values persist in `AppStorage`, so this order is storage identity: append only.
case apps
case mcp
case skills
static let storageKey = "appsSectionDestination"
var id: Int { rawValue }
var title: String {
switch self {
case .apps: return "Apps"
case .mcp: return "MCP"
case .skills: return "Skills"
}
}
/// Stable suffix for accessibility identifiers, so selectors do not ride on raw values.
var automationID: String {
switch self {
case .apps: return "apps"
case .mcp: return "mcp"
case .skills: return "skills"
}
}
}
/// The Apps section chips, sized to sit in the page toolbar's trailing actions slot.
struct AppsSectionNavigation: View {
let selected: AppsSectionDestination
let onSelect: (AppsSectionDestination) -> Void
var body: some View {
HStack(spacing: QueryShellLayout.chipSpacing) {
ForEach(AppsSectionDestination.allCases) { section in
AppsSectionChip(
title: section.title,
isActive: section == selected,
action: { onSelect(section) }
)
.accessibilityIdentifier("apps-section-\(section.automationID)")
}
}
.accessibilityElement(children: .contain)
.accessibilityIdentifier("apps-section-navigation")
}
}
/// Visually identical to Brain's section chip; kept local so the toolbar row can own its own
/// height without Brain's page layout having to accommodate a second caller.
private struct AppsSectionChip: View {
let title: String
let isActive: Bool
let action: () -> Void
@State private var isHovering = false
var body: some View {
Button(action: action) {
Text(title)
.scaledFont(size: OmiType.caption, weight: isActive ? .semibold : .regular)
.foregroundStyle(GlassShell.controlLabel(isProminent: isActive || isHovering))
.padding(.horizontal, 12)
.frame(height: QueryShellLayout.chipHeight)
.glassChip(isActive: isActive)
}
.buttonStyle(.plain)
.contentShape(Capsule(style: .continuous))
.onHover { isHovering = $0 }
.animation(InkReduceMotion.animation(.easeOut(duration: InkMotion.press)), value: isActive)
.accessibilityAddTraits(isActive ? .isSelected : [])
}
}