Kacheln und Fenster sprechen dieselbe Sprache
Alle Kacheln, Popovers und Fenster auf die Designschicht umgestellt: rund hundert gesetzte Schriftgrößen und 159 gesetzte Abstände und Radien sind jetzt Token. Übrig bleibt `spacing: 0` — das ist eine Aussage, keine geratene Zahl. Die Einstellungen bekommen eine Seitenleiste statt sieben Reitern, deren deutsche Beschriftungen zusammen 880 Punkte brauchten. Panel-Token stehen nicht mehr in Fenstern: dort gilt Systemsemantik, damit der Hellmodus nicht schwarz auf schwarz endet. Neu ist ein Schaufenster, das alle Bauteile nebeneinander zeigt — ohne das lässt sich eine Änderung an einem Token nur prüfen, indem man die App bedient, bis jede Kachel einmal zu sehen war. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9b600de9a9
commit
edb327cabe
@@ -45,9 +45,9 @@ public struct AirDropView: View {
|
||||
if compact {
|
||||
// Nebeneinander statt übereinander: in 52 Punkten Höhe ist für
|
||||
// Symbol über Text kein Platz.
|
||||
HStack(spacing: 8) {
|
||||
HStack(spacing: Onyx.Space.md) {
|
||||
Image(systemName: "shareplay")
|
||||
.font(.system(size: 15, weight: .light))
|
||||
.font(.system(size: Onyx.Symbol.large, weight: .light))
|
||||
.foregroundStyle(isTargeted ? Onyx.Color.accent
|
||||
: Onyx.Color.textTertiary)
|
||||
Text(failed ? "airdrop.unavailable" : "airdrop.hint", bundle: .module)
|
||||
@@ -57,11 +57,11 @@ public struct AirDropView: View {
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.horizontal, Onyx.Space.md)
|
||||
} else {
|
||||
VStack(spacing: 6) {
|
||||
VStack(spacing: Onyx.Space.sm) {
|
||||
Image(systemName: "shareplay")
|
||||
.font(.system(size: 20, weight: .light))
|
||||
.font(.system(size: Onyx.Symbol.xlarge, weight: .light))
|
||||
.foregroundStyle(isTargeted ? Onyx.Color.accent
|
||||
: Onyx.Color.textTertiary)
|
||||
Text(failed ? "airdrop.unavailable" : "airdrop.hint", bundle: .module)
|
||||
@@ -74,15 +74,12 @@ public struct AirDropView: View {
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.strokeBorder(isTargeted ? Onyx.Color.accent : Onyx.Color.hairline,
|
||||
style: StrokeStyle(lineWidth: 1, dash: [4, 3]))
|
||||
}
|
||||
// Diese Fläche ist **nur** zum Ablegen da. Ihre Kante steht deshalb
|
||||
// immer — ohne sie wüsste niemand, dass sie eine ist.
|
||||
.onyxDropZone(isTargeted: isTargeted, style: .always)
|
||||
// Derselbe Weg wie in der Ablage: auch hier kommen Anhänge als
|
||||
// Versprechen an, nicht als fertige Datei.
|
||||
.contentShape(.rect)
|
||||
.onFileDrop(isTargeted: $isTargeted) { urls in failed = !AirDrop.send(urls) }
|
||||
.animation(Onyx.Motion.value, value: isTargeted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +50,12 @@ public struct ShelfView: View {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
VStack(alignment: .leading, spacing: Onyx.Space.sm) {
|
||||
if store.items.isEmpty {
|
||||
EmptyShelf()
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 4) {
|
||||
LazyVStack(alignment: .leading, spacing: Onyx.Space.xs) {
|
||||
ForEach(store.items.prefix(compact ? 4 : 20)) { item in
|
||||
ShelfRow(item: item, store: store, thumbnails: thumbnails)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public struct ShelfView: View {
|
||||
}
|
||||
.scrollIndicators(.never)
|
||||
|
||||
HStack(spacing: 10) {
|
||||
HStack(spacing: Onyx.Space.row) {
|
||||
Button { store.removeAll() } label: {
|
||||
Text("shelf.clear", bundle: .module).font(Onyx.Font.caption)
|
||||
}
|
||||
@@ -82,24 +82,17 @@ public struct ShelfView: View {
|
||||
// Die ganze Kachel nimmt Dateien an, nicht nur ein Streifen darin.
|
||||
.contentShape(.rect)
|
||||
.onFileDrop(isTargeted: $isTargeted) { urls in store.add(urls) }
|
||||
.overlay {
|
||||
if isTargeted {
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.strokeBorder(Onyx.Color.accent, style: StrokeStyle(lineWidth: 1.5, dash: [4, 3]))
|
||||
.background(Onyx.Color.accent.opacity(0.08),
|
||||
in: .rect(cornerRadius: 10, style: .continuous))
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
.animation(Onyx.Motion.value, value: isTargeted)
|
||||
// Die Kachel nimmt nebenbei Dateien an — die Kante erscheint deshalb
|
||||
// erst, wenn wirklich etwas darüber schwebt.
|
||||
.onyxDropZone(isTargeted: isTargeted, style: .whenTargeted)
|
||||
}
|
||||
}
|
||||
|
||||
private struct EmptyShelf: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
VStack(alignment: .leading, spacing: Onyx.Space.xs) {
|
||||
Image(systemName: "tray")
|
||||
.font(.system(size: 16))
|
||||
.font(.system(size: Onyx.Symbol.large))
|
||||
.foregroundStyle(Onyx.Color.textTertiary)
|
||||
Text("shelf.empty", bundle: .module)
|
||||
.font(Onyx.Font.caption)
|
||||
@@ -117,9 +110,9 @@ private struct ShelfRow: View {
|
||||
@State private var isHovered = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 8) {
|
||||
HStack(spacing: Onyx.Space.md) {
|
||||
thumbnail
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
VStack(alignment: .leading, spacing: Onyx.Space.xxs) {
|
||||
Text(item.name)
|
||||
.font(Onyx.Font.caption)
|
||||
.foregroundStyle(Onyx.Color.textPrimary)
|
||||
@@ -133,19 +126,19 @@ private struct ShelfRow: View {
|
||||
|
||||
if isHovered {
|
||||
Button { AirDrop.send([item.url]) } label: {
|
||||
Image(systemName: "shareplay").font(.system(size: 10))
|
||||
Image(systemName: "shareplay").font(.system(size: Onyx.Symbol.small))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(Onyx.Color.textTertiary)
|
||||
|
||||
Button { store.remove(item) } label: {
|
||||
Image(systemName: "xmark.circle.fill").font(.system(size: 11))
|
||||
Image(systemName: "xmark.circle.fill").font(.system(size: Onyx.Symbol.small))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(Onyx.Color.textTertiary)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
.padding(.vertical, Onyx.Space.xxs)
|
||||
.contentShape(.rect)
|
||||
.onHover { isHovered = $0 }
|
||||
// `NSItemProvider(contentsOf:)` statt `.draggable(item.url)`: letzteres
|
||||
@@ -176,9 +169,9 @@ private struct ShelfRow: View {
|
||||
Image(nsImage: image)
|
||||
.resizable().aspectRatio(contentMode: .fit)
|
||||
.frame(width: 22, height: 22)
|
||||
.clipShape(.rect(cornerRadius: 3, style: .continuous))
|
||||
.clipShape(.rect(cornerRadius: Onyx.Radius.bar, style: .continuous))
|
||||
} else {
|
||||
RoundedRectangle(cornerRadius: 3, style: .continuous)
|
||||
RoundedRectangle(cornerRadius: Onyx.Radius.bar, style: .continuous)
|
||||
.fill(Onyx.Color.elevated)
|
||||
.frame(width: 22, height: 22)
|
||||
}
|
||||
@@ -204,13 +197,13 @@ public struct ShelfWidget: OnyxWidget {
|
||||
|
||||
public func makeView() -> AnyView {
|
||||
AnyView(
|
||||
VStack(spacing: 8) {
|
||||
VStack(spacing: Onyx.Space.md) {
|
||||
// AirDrop oben und schmal: es ist eine reine Ablegefläche ohne
|
||||
// Zustand, die Ablage darunter braucht den Platz für ihre Liste.
|
||||
AirDropView(compact: true)
|
||||
.frame(height: 52)
|
||||
|
||||
Divider().overlay(Onyx.Color.hairline)
|
||||
OnyxHairline()
|
||||
|
||||
ShelfView(store: store)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user