Style fixes for NavigationLink, DisclosureGroup, Button, TextField, Picker, and more

This commit is contained in:
Carson Katri 2020-08-03 17:07:44 -04:00
parent cdc452b7a5
commit abc59f048a
6 changed files with 138 additions and 31 deletions

View File

@ -0,0 +1,74 @@
// Copyright 2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Carson Katri on 8/2/20.
//
public struct _NavigationLinkStyleConfiguration: View {
public let body: AnyView
public let isSelected: Bool
}
public protocol _NavigationLinkStyle {
associatedtype Body: View
typealias Configuration = _NavigationLinkStyleConfiguration
func makeBody(configuration: Configuration) -> Self.Body
}
public struct _DefaultNavigationLinkStyle: _NavigationLinkStyle {
public func makeBody(configuration: Configuration) -> some View {
configuration.foregroundColor(.accentColor)
}
}
public struct _AnyNavigationLinkStyle: _NavigationLinkStyle {
public typealias Body = AnyView
private let bodyClosure: (_NavigationLinkStyleConfiguration) -> AnyView
public let type: Any.Type
public init<S: _NavigationLinkStyle>(_ style: S) {
type = S.self
bodyClosure = { configuration in
AnyView(style.makeBody(configuration: configuration))
}
}
public func makeBody(configuration: Configuration) -> AnyView {
bodyClosure(configuration)
}
}
public enum _NavigationLinkStyleKey: EnvironmentKey {
public static var defaultValue: _AnyNavigationLinkStyle {
_AnyNavigationLinkStyle(_DefaultNavigationLinkStyle())
}
}
extension EnvironmentValues {
var _navigationLinkStyle: _AnyNavigationLinkStyle {
get {
self[_NavigationLinkStyleKey.self]
}
set {
self[_NavigationLinkStyleKey.self] = newValue
}
}
}
extension View {
public func _navigationLinkStyle<S: _NavigationLinkStyle>(_ style: S) -> some View {
environment(\._navigationLinkStyle, _AnyNavigationLinkStyle(style))
}
}

View File

@ -19,7 +19,8 @@ public struct NavigationLink<Label, Destination>: View where Label: View, Destin
let destination: Destination let destination: Destination
let label: Label let label: Label
@Environment(_navigationDestinationKey) var navigationContext @Environment(\.navigationDestination) var navigationContext
@Environment(\._navigationLinkStyle) var style
public init(destination: Destination, @ViewBuilder label: () -> Label) { public init(destination: Destination, @ViewBuilder label: () -> Label) {
self.destination = destination self.destination = destination
@ -46,8 +47,7 @@ extension NavigationLink where Label == Text {
/// Creates an instance that presents `destination`, with a `Text` label /// Creates an instance that presents `destination`, with a `Text` label
/// generated from a title string. /// generated from a title string.
public init<S>(_ title: S, destination: Destination) where S: StringProtocol { public init<S>(_ title: S, destination: Destination) where S: StringProtocol {
self.destination = destination self.init(destination: destination) { Text(title) }
label = Text(title)
} }
/// Creates an instance that presents `destination` when active, with a /// Creates an instance that presents `destination` when active, with a
@ -71,7 +71,15 @@ public struct _NavigationLinkProxy<Label, Destination> where Label: View, Destin
public init(_ subject: NavigationLink<Label, Destination>) { self.subject = subject } public init(_ subject: NavigationLink<Label, Destination>) { self.subject = subject }
public var label: Label { subject.label } public var label: AnyView {
subject.style.makeBody(configuration: .init(body: AnyView(subject.label),
isSelected: isSelected))
}
public var style: _AnyNavigationLinkStyle { subject.style }
public var isSelected: Bool {
true
}
public func activate() { public func activate() {
subject.navigationContext!.wrappedValue = AnyView(subject.destination) subject.navigationContext!.wrappedValue = AnyView(subject.destination)

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
import TokamakCore import TokamakCore
import TokamakStaticHTML
extension NavigationLink: ViewDeferredToRenderer { extension NavigationLink: ViewDeferredToRenderer {
public var deferredBody: AnyView { public var deferredBody: AnyView {
@ -20,11 +21,16 @@ extension NavigationLink: ViewDeferredToRenderer {
return AnyView( return AnyView(
DynamicHTML("a", [ DynamicHTML("a", [
"href": "javascript:void%200", "href": "javascript:void%200",
"style": proxy.style.type == _SidebarNavigationLinkStyle.self ?
"width: 100%; text-decoration: none;"
: "",
], listeners: [ ], listeners: [
// FIXME: Focus destination or something so assistive // FIXME: Focus destination or something so assistive
// technology knows where to look when clicking the link. // technology knows where to look when clicking the link.
"click": { _ in proxy.activate() }, "click": { _ in proxy.activate() },
]) { proxy.label } ]) {
proxy.label
}
) )
} }
} }

View File

@ -101,7 +101,8 @@ var links: [NavItem] {
Counter(count: Count(value: 5), limit: 15) Counter(count: Count(value: 5), limit: 15)
.padding() .padding()
.background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 1.0)) .background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 1.0))
.border(Color.red, width: 3)), .border(Color.red, width: 3)
.foregroundColor(.black)),
NavItem("ZStack", destination: ZStack { NavItem("ZStack", destination: ZStack {
Text("I'm on bottom") Text("I'm on bottom")
Text("I'm forced to the top") Text("I'm forced to the top")

View File

@ -77,32 +77,23 @@ public let tokamakStyles = """
border-radius: .1em; border-radius: .1em;
} }
._tokamak-buttonstyle-default,
._tokamak-securefield,
._tokamak-textfield-default,
._tokamak-textfield-roundedborder,
._tokamak-picker {
color-scheme: light dark;
}
@media (prefers-color-scheme:dark) { @media (prefers-color-scheme:dark) {
._tokamak-buttonstyle-default {
background-color: rgb(99, 95, 98);
}
._tokamak-securefield {
background-color: rgb(99, 95, 98);
color: #FFFFFF;
}
._tokamak-textfield-default {
background-color: rgb(99, 95, 98);
color: #FFFFFF;
}
._tokamak-textfield-roundedborder {
background-color: rgb(99, 95, 98);
color: #FFFFFF;
}
._tokamak-picker {
background-color: rgb(99, 95, 98);
color: #FFFFFF;
}
._tokamak-text-redacted::after { ._tokamak-text-redacted::after {
background-color: rgb(100, 100, 100); background-color: rgb(100, 100, 100);
} }
._tokamak-disclosuregroup-chevron {
border-right-color: rgba(255, 255, 255, 0.25);
border-top-color: rgba(255, 255, 255, 0.25);
}
} }
""" """

View File

@ -90,16 +90,43 @@ extension InsetGroupedListStyle: ListStyleDeferredToRenderer {
} }
extension SidebarListStyle: ListStyleDeferredToRenderer { extension SidebarListStyle: ListStyleDeferredToRenderer {
public func listRow<Row>(_ row: Row) -> AnyView where Row: View {
AnyView(row)
}
public func listBody<ListBody>(_ content: ListBody) -> AnyView where ListBody: View { public func listBody<ListBody>(_ content: ListBody) -> AnyView where ListBody: View {
AnyView(content AnyView(content
.padding(.all) ._navigationLinkStyle(_SidebarNavigationLinkStyle())
.padding(.leading, 20) .padding([.horizontal, .top], 6)
.background(Color._withScheme { .background(Color._withScheme {
switch $0 { switch $0 {
case .light: return Color(0xF2F2F7) case .light: return Color(0xF2F2F7)
case .dark: return Color(.sRGB, red: 45 / 255, green: 43 / 255, blue: 48 / 255) case .dark: return Color(0x2D2B30)
} }
}) })
) )
} }
} }
public struct _SidebarNavigationLinkStyle: _NavigationLinkStyle {
@ViewBuilder
public func makeBody(configuration: _NavigationLinkStyleConfiguration) -> some View {
if configuration.isSelected {
configuration
.padding(6)
.font(.footnote)
.background(Color._withScheme {
switch $0 {
case .light: return Color(.sRGB, white: 0, opacity: 0.1)
case .dark: return Color(.sRGB, white: 1, opacity: 0.1)
}
})
.cornerRadius(5)
} else {
configuration
.padding(6)
.foregroundColor(.primary)
.font(.footnote)
}
}
}