forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAppsPageHeaderControls.swift
More file actions
149 lines (135 loc) · 4.24 KB
/
Copy pathAppsPageHeaderControls.swift
File metadata and controls
149 lines (135 loc) · 4.24 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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import OmiTheme
import SwiftUI
enum AppsHeaderMetrics {
static let searchFieldMaxWidth: CGFloat = 360
static let searchFieldMinWidth: CGFloat = 200
static let controlIconSize: CGFloat = 16
static let controlHeight: CGFloat = 44
}
/// Keeps the flexible search field from squeezing labelled controls. At compact
/// widths the same controls stack instead of truncating.
struct AppsHeaderRow<Search: View, Filters: View, Create: View, Dismiss: View>: View {
let search: Search
let filters: Filters
let create: Create
let dismiss: Dismiss
init(
@ViewBuilder search: () -> Search,
@ViewBuilder filters: () -> Filters,
@ViewBuilder create: () -> Create,
@ViewBuilder dismiss: () -> Dismiss
) {
self.search = search()
self.filters = filters()
self.create = create()
self.dismiss = dismiss()
}
var body: some View {
ViewThatFits(in: .horizontal) {
HStack(spacing: OmiSpacing.md) {
search
.frame(
minWidth: AppsHeaderMetrics.searchFieldMinWidth,
maxWidth: AppsHeaderMetrics.searchFieldMaxWidth
)
filters
.fixedSize(horizontal: true, vertical: false)
create
dismiss
}
VStack(alignment: .leading, spacing: OmiSpacing.sm) {
HStack(spacing: OmiSpacing.sm) {
search.frame(minWidth: AppsHeaderMetrics.searchFieldMinWidth)
dismiss
}
HStack(spacing: OmiSpacing.sm) {
filters
.fixedSize(horizontal: true, vertical: false)
create
}
}
// The real filter controls have a fixed minimum width. Once they no
// longer fit with Create App, give each control group its own row.
VStack(alignment: .leading, spacing: OmiSpacing.sm) {
HStack(spacing: OmiSpacing.sm) {
search
dismiss
}
filters
.fixedSize(horizontal: true, vertical: false)
create
}
}
}
}
struct FilterToggle: View {
let icon: String
let label: String
let isActive: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: OmiSpacing.xs) {
Image(systemName: icon)
.scaledFont(size: OmiType.caption)
.frame(width: AppsHeaderMetrics.controlIconSize)
Text(label)
.scaledFont(size: OmiType.body)
.lineLimit(1)
}
.padding(.horizontal, OmiSpacing.md)
.frame(height: AppsHeaderMetrics.controlHeight)
.background(
Capsule(style: .continuous)
.fill(isActive ? Ink.rowFillHover : Ink.rowFill)
.overlay(
Capsule(style: .continuous)
.stroke(isActive ? Ink.hairline : Ink.separator, lineWidth: 1)
)
)
.foregroundColor(isActive ? Ink.primary : Ink.secondary)
.fixedSize(horizontal: true, vertical: false)
}
.buttonStyle(.plain)
}
}
struct SmallHeaderButton: View {
let icon: String
let label: String
let color: Color
let action: () -> Void
@State private var isHovering = false
var body: some View {
Button(action: action) {
HStack(spacing: OmiSpacing.xs) {
Image(systemName: icon)
.scaledFont(size: OmiType.caption)
.frame(width: AppsHeaderMetrics.controlIconSize)
.foregroundColor(color)
Text(label)
.scaledFont(size: OmiType.body, weight: .medium)
.foregroundColor(Ink.secondary)
.lineLimit(1)
}
.padding(.horizontal, OmiSpacing.md)
.frame(height: AppsHeaderMetrics.controlHeight)
.background(
Capsule(style: .continuous)
.fill(isHovering ? Ink.rowFillHover : Ink.rowFill)
.overlay(
Capsule(style: .continuous)
.stroke(isHovering ? Ink.hairline : Ink.separator, lineWidth: 1)
)
)
.fixedSize(horizontal: true, vertical: false)
}
.buttonStyle(.plain)
// The inner label needs its own fixed height for the capsule background,
// while this outer frame makes the button's AppKit layout footprint match
// the adjacent search and filter controls.
.frame(height: AppsHeaderMetrics.controlHeight)
.onHover { hovering in
OmiMotion.withGated(.easeOut(duration: 0.12)) { isHovering = hovering }
}
}
}