Add dark styles for most elements
This commit is contained in:
parent
c7b5e75e1a
commit
cdc452b7a5
|
@ -14,16 +14,23 @@
|
||||||
|
|
||||||
/// A value with a modifier applied to it.
|
/// A value with a modifier applied to it.
|
||||||
public struct ModifiedContent<Content, Modifier> {
|
public struct ModifiedContent<Content, Modifier> {
|
||||||
|
@Environment(\.self) public var environment
|
||||||
public typealias Body = Never
|
public typealias Body = Never
|
||||||
public let content: Content
|
public private(set) var content: Content
|
||||||
public let modifier: Modifier
|
public private(set) var modifier: Modifier
|
||||||
|
|
||||||
@inlinable public init(content: Content, modifier: Modifier) {
|
public init(content: Content, modifier: Modifier) {
|
||||||
self.content = content
|
self.content = content
|
||||||
self.modifier = modifier
|
self.modifier = modifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ModifiedContent: EnvironmentReader where Modifier: EnvironmentReader {
|
||||||
|
mutating func setContent(from values: EnvironmentValues) {
|
||||||
|
modifier.setContent(from: values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ModifiedContent: View, ParentView where Content: View, Modifier: ViewModifier {
|
extension ModifiedContent: View, ParentView where Content: View, Modifier: ViewModifier {
|
||||||
public var body: Body {
|
public var body: Body {
|
||||||
neverBody("ModifiedContent<View, ViewModifier>")
|
neverBody("ModifiedContent<View, ViewModifier>")
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
// Created by Carson Katri on 6/29/20.
|
// Created by Carson Katri on 6/29/20.
|
||||||
//
|
//
|
||||||
|
|
||||||
public struct _BackgroundModifier<Background>: ViewModifier where Background: View {
|
public struct _BackgroundModifier<Background>: ViewModifier, EnvironmentReader
|
||||||
|
where Background: View {
|
||||||
|
public var environment: EnvironmentValues!
|
||||||
public var background: Background
|
public var background: Background
|
||||||
public var alignment: Alignment
|
public var alignment: Alignment
|
||||||
|
|
||||||
|
@ -27,9 +29,18 @@ public struct _BackgroundModifier<Background>: ViewModifier where Background: Vi
|
||||||
public func body(content: Content) -> some View {
|
public func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func setContent(from values: EnvironmentValues) {
|
||||||
|
environment = values
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension _BackgroundModifier: Equatable where Background: Equatable {}
|
extension _BackgroundModifier: Equatable where Background: Equatable {
|
||||||
|
public static func == (lhs: _BackgroundModifier<Background>,
|
||||||
|
rhs: _BackgroundModifier<Background>) -> Bool {
|
||||||
|
lhs.background == rhs.background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
public func background<Background>(_ background: Background, alignment: Alignment = .center) -> some View where Background: View {
|
public func background<Background>(_ background: Background, alignment: Alignment = .center) -> some View where Background: View {
|
||||||
|
@ -37,7 +48,9 @@ extension View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct _OverlayModifier<Overlay>: ViewModifier where Overlay: View {
|
public struct _OverlayModifier<Overlay>: ViewModifier, EnvironmentReader
|
||||||
|
where Overlay: View {
|
||||||
|
public var environment: EnvironmentValues!
|
||||||
public var overlay: Overlay
|
public var overlay: Overlay
|
||||||
public var alignment: Alignment
|
public var alignment: Alignment
|
||||||
|
|
||||||
|
@ -52,9 +65,18 @@ public struct _OverlayModifier<Overlay>: ViewModifier where Overlay: View {
|
||||||
overlay
|
overlay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func setContent(from values: EnvironmentValues) {
|
||||||
|
environment = values
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension _OverlayModifier: Equatable where Overlay: Equatable {}
|
extension _OverlayModifier: Equatable where Overlay: Equatable {
|
||||||
|
public static func == (lhs: _OverlayModifier<Overlay>,
|
||||||
|
rhs: _OverlayModifier<Overlay>) -> Bool {
|
||||||
|
lhs.overlay == rhs.overlay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
public func overlay<Overlay>(_ overlay: Overlay,
|
public func overlay<Overlay>(_ overlay: Overlay,
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
public struct _StrokedShape<S>: Shape where S: Shape {
|
public struct _StrokedShape<S>: Shape where S: Shape {
|
||||||
|
@Environment(\.self) public var environment
|
||||||
public var shape: S
|
public var shape: S
|
||||||
public var style: StrokeStyle
|
public var style: StrokeStyle
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ public struct FillStyle: Equatable, ShapeStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct _ShapeView<Content, Style>: View where Content: Shape, Style: ShapeStyle {
|
public struct _ShapeView<Content, Style>: View where Content: Shape, Style: ShapeStyle {
|
||||||
|
@Environment(\.self) public var environment
|
||||||
@Environment(\.foregroundColor) public var foregroundColor
|
@Environment(\.foregroundColor) public var foregroundColor
|
||||||
public var shape: Content
|
public var shape: Content
|
||||||
public var style: Style
|
public var style: Style
|
||||||
|
|
|
@ -16,8 +16,18 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
public struct Color: Hashable, Equatable {
|
public struct Color: Hashable, Equatable {
|
||||||
// FIXME: This is not injected.
|
public static func == (lhs: Self, rhs: Self) -> Bool {
|
||||||
@Environment(\.accentColor) static var envAccentColor
|
var lightEnv = EnvironmentValues()
|
||||||
|
lightEnv.colorScheme = .light
|
||||||
|
var darkEnv = EnvironmentValues()
|
||||||
|
darkEnv.colorScheme = .dark
|
||||||
|
return lhs._evaluate(lightEnv) == rhs._evaluate(lightEnv) &&
|
||||||
|
lhs._evaluate(darkEnv) == rhs._evaluate(darkEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(evaluator(EnvironmentValues()))
|
||||||
|
}
|
||||||
|
|
||||||
public enum RGBColorSpace {
|
public enum RGBColorSpace {
|
||||||
case sRGB
|
case sRGB
|
||||||
|
@ -25,32 +35,42 @@ public struct Color: Hashable, Equatable {
|
||||||
case displayP3
|
case displayP3
|
||||||
}
|
}
|
||||||
|
|
||||||
public let red: Double
|
public struct _RGBA: Hashable, Equatable {
|
||||||
public let green: Double
|
public let red: Double
|
||||||
public let blue: Double
|
public let green: Double
|
||||||
public let opacity: Double
|
public let blue: Double
|
||||||
public let space: RGBColorSpace
|
public let opacity: Double
|
||||||
|
public let space: RGBColorSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
let evaluator: (EnvironmentValues) -> _RGBA
|
||||||
|
|
||||||
|
private init(_ evaluator: @escaping (EnvironmentValues) -> _RGBA) {
|
||||||
|
self.evaluator = evaluator
|
||||||
|
}
|
||||||
|
|
||||||
public init(_ colorSpace: RGBColorSpace = .sRGB,
|
public init(_ colorSpace: RGBColorSpace = .sRGB,
|
||||||
red: Double,
|
red: Double,
|
||||||
green: Double,
|
green: Double,
|
||||||
blue: Double,
|
blue: Double,
|
||||||
opacity: Double = 1) {
|
opacity: Double = 1) {
|
||||||
self.red = red
|
self.init { _ in
|
||||||
self.green = green
|
_RGBA(red: red,
|
||||||
self.blue = blue
|
green: green,
|
||||||
self.opacity = opacity
|
blue: blue,
|
||||||
space = colorSpace
|
opacity: opacity,
|
||||||
|
space: colorSpace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(_ colorSpace: RGBColorSpace = .sRGB,
|
public init(_ colorSpace: RGBColorSpace = .sRGB,
|
||||||
white: Double,
|
white: Double,
|
||||||
opacity: Double = 1) {
|
opacity: Double = 1) {
|
||||||
red = white
|
self.init(colorSpace,
|
||||||
green = white
|
red: white,
|
||||||
blue = white
|
green: white,
|
||||||
self.opacity = opacity
|
blue: white,
|
||||||
space = colorSpace
|
opacity: opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source for the formula:
|
// Source for the formula:
|
||||||
|
@ -60,15 +80,26 @@ public struct Color: Hashable, Equatable {
|
||||||
brightness: Double,
|
brightness: Double,
|
||||||
opacity: Double = 1) {
|
opacity: Double = 1) {
|
||||||
let a = saturation * min(brightness / 2, 1 - (brightness / 2))
|
let a = saturation * min(brightness / 2, 1 - (brightness / 2))
|
||||||
let f: (Int) -> Double = { n in
|
let f = { (n: Int) -> Double in
|
||||||
let k = Double((n + Int(hue * 12)) % 12)
|
let k = Double((n + Int(hue * 12)) % 12)
|
||||||
return brightness - (a * max(-1, min(k - 3, 9 - k, 1)))
|
return brightness - (a * max(-1, min(k - 3, 9 - k, 1)))
|
||||||
}
|
}
|
||||||
red = f(0)
|
self.init(.sRGB,
|
||||||
green = f(8)
|
red: f(0),
|
||||||
blue = f(4)
|
green: f(8),
|
||||||
self.opacity = opacity
|
blue: f(4),
|
||||||
space = .sRGB
|
opacity: opacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `Color` dependent on the current `ColorScheme`.
|
||||||
|
public static func _withScheme(_ evaluator: @escaping (ColorScheme) -> Self) -> Self {
|
||||||
|
.init {
|
||||||
|
evaluator($0.colorScheme)._evaluate($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func _evaluate(_ environment: EnvironmentValues) -> _RGBA {
|
||||||
|
evaluator(environment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +115,27 @@ extension Color {
|
||||||
public static let yellow: Self = .init(red: 1.00, green: 0.84, blue: 0.04)
|
public static let yellow: Self = .init(red: 1.00, green: 0.84, blue: 0.04)
|
||||||
public static let pink: Self = .init(red: 1.00, green: 0.22, blue: 0.37)
|
public static let pink: Self = .init(red: 1.00, green: 0.22, blue: 0.37)
|
||||||
public static let purple: Self = .init(red: 0.75, green: 0.36, blue: 0.95)
|
public static let purple: Self = .init(red: 0.75, green: 0.36, blue: 0.95)
|
||||||
// FIXME: Switch to use colorScheme
|
public static let primary: Self = .init {
|
||||||
public static let primary: Self = .black
|
switch $0.colorScheme {
|
||||||
|
case .light:
|
||||||
|
return .init(red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 0,
|
||||||
|
opacity: 1,
|
||||||
|
space: .sRGB)
|
||||||
|
case .dark:
|
||||||
|
return .init(red: 1,
|
||||||
|
green: 1,
|
||||||
|
blue: 1,
|
||||||
|
opacity: 1,
|
||||||
|
space: .sRGB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static let secondary: Self = .gray
|
public static let secondary: Self = .gray
|
||||||
|
public static let accentColor: Self = .init {
|
||||||
|
($0.accentColor ?? Self.blue)._evaluate($0)
|
||||||
|
}
|
||||||
|
|
||||||
public init(_ color: UIColor) {
|
public init(_ color: UIColor) {
|
||||||
self = color.color
|
self = color.color
|
||||||
|
@ -96,11 +145,11 @@ extension Color {
|
||||||
extension Color: ExpressibleByIntegerLiteral {
|
extension Color: ExpressibleByIntegerLiteral {
|
||||||
/// Allows initializing value of `Color` type from hex values
|
/// Allows initializing value of `Color` type from hex values
|
||||||
public init(integerLiteral bitMask: UInt32) {
|
public init(integerLiteral bitMask: UInt32) {
|
||||||
red = Double((bitMask & 0xFF0000) >> 16) / 255
|
self.init(.sRGB,
|
||||||
green = Double((bitMask & 0x00FF00) >> 8) / 255
|
red: Double((bitMask & 0xFF0000) >> 16) / 255,
|
||||||
blue = Double(bitMask & 0x0000FF) / 255
|
green: Double((bitMask & 0x00FF00) >> 8) / 255,
|
||||||
opacity = 1
|
blue: Double(bitMask & 0x0000FF) / 255,
|
||||||
space = .sRGB
|
opacity: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,11 +166,11 @@ extension Color {
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self.red = Double(red) / 255
|
self.init(.sRGB,
|
||||||
self.green = Double(green) / 255
|
red: Double(red) / 255,
|
||||||
self.blue = Double(blue) / 255
|
green: Double(green) / 255,
|
||||||
opacity = 1
|
blue: Double(blue) / 255,
|
||||||
space = .sRGB
|
opacity: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,12 +202,6 @@ extension View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Color {
|
|
||||||
public static var accentColor: Self {
|
|
||||||
envAccentColor ?? .blue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ForegroundColorKey: EnvironmentKey {
|
struct ForegroundColorKey: EnvironmentKey {
|
||||||
static let defaultValue: Color? = nil
|
static let defaultValue: Color? = nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
/// A horizontal line for separating content.
|
/// A horizontal line for separating content.
|
||||||
public struct Divider: View {
|
public struct Divider: View {
|
||||||
|
@Environment(\.self) public var environment
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: Never {
|
public var body: Never {
|
||||||
neverBody("Divider")
|
neverBody("Divider")
|
||||||
|
|
|
@ -33,6 +33,7 @@ public struct Text: View {
|
||||||
let storage: _Storage
|
let storage: _Storage
|
||||||
let modifiers: [_Modifier]
|
let modifiers: [_Modifier]
|
||||||
|
|
||||||
|
@Environment(\.self) public var environment
|
||||||
@Environment(\.font) var font
|
@Environment(\.font) var font
|
||||||
@Environment(\.foregroundColor) var foregroundColor
|
@Environment(\.foregroundColor) var foregroundColor
|
||||||
@Environment(\.redactionReasons) var redactionReasons
|
@Environment(\.redactionReasons) var redactionReasons
|
||||||
|
|
|
@ -30,7 +30,7 @@ extension App {
|
||||||
///
|
///
|
||||||
public static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues) {
|
public static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues) {
|
||||||
let body = TokamakDOM.body
|
let body = TokamakDOM.body
|
||||||
if body.style == .undefined {
|
if body.style.object!.all == "" {
|
||||||
body.style = "margin: 0;"
|
body.style = "margin: 0;"
|
||||||
}
|
}
|
||||||
let rootStyle = document.createElement!("style").object!
|
let rootStyle = document.createElement!("style").object!
|
||||||
|
@ -44,7 +44,7 @@ extension App {
|
||||||
_ = body.appendChild!(div)
|
_ = body.appendChild!(div)
|
||||||
|
|
||||||
ScenePhaseObserver.observe()
|
ScenePhaseObserver.observe()
|
||||||
ColorSchemeObserver.observe()
|
ColorSchemeObserver.observe(div)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func _setTitle(_ title: String) {
|
public static func _setTitle(_ title: String) {
|
||||||
|
|
|
@ -21,13 +21,23 @@ enum ColorSchemeObserver {
|
||||||
)
|
)
|
||||||
|
|
||||||
private static var closure: JSClosure?
|
private static var closure: JSClosure?
|
||||||
|
private static var cancellable: AnyCancellable?
|
||||||
|
|
||||||
static func observe() {
|
static func observe(_ rootElement: JSObjectRef) {
|
||||||
let closure = JSClosure {
|
let closure = JSClosure {
|
||||||
publisher.value = .init(matchMediaDarkScheme: $0[0].object!)
|
publisher.value = .init(matchMediaDarkScheme: $0[0].object!)
|
||||||
return .undefined
|
return .undefined
|
||||||
}
|
}
|
||||||
_ = matchMediaDarkScheme.addEventListener!("change", closure)
|
_ = matchMediaDarkScheme.addEventListener!("change", closure)
|
||||||
Self.closure = closure
|
Self.closure = closure
|
||||||
|
Self.cancellable = Self.publisher.sink { colorScheme in
|
||||||
|
let systemBackground = { () -> String in
|
||||||
|
switch colorScheme {
|
||||||
|
case .light: return "#FFFFFF"
|
||||||
|
case .dark: return "rgb(38, 38, 38)"
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
rootElement.style.object!.backgroundColor = .string("\(systemBackground)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ extension _Button: ViewDeferredToRenderer where Label == Text {
|
||||||
public var deferredBody: AnyView {
|
public var deferredBody: AnyView {
|
||||||
let attributes: [String: String]
|
let attributes: [String: String]
|
||||||
if buttonStyle.type == DefaultButtonStyle.self {
|
if buttonStyle.type == DefaultButtonStyle.self {
|
||||||
attributes = [:]
|
attributes = ["class": "_tokamak-buttonstyle-default"]
|
||||||
} else {
|
} else {
|
||||||
attributes = ["class": "_tokamak-buttonstyle-reset"]
|
attributes = ["class": "_tokamak-buttonstyle-reset"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ extension _PickerContainer: ViewDeferredToRenderer {
|
||||||
AnyView(HTML("label") {
|
AnyView(HTML("label") {
|
||||||
label
|
label
|
||||||
Text(" ")
|
Text(" ")
|
||||||
DynamicHTML("select", listeners: ["change": {
|
DynamicHTML("select", ["class": "_tokamak-picker"], listeners: ["change": {
|
||||||
guard
|
guard
|
||||||
let valueString = $0.target.object!.value.string,
|
let valueString = $0.target.object!.value.string,
|
||||||
let value = Int(valueString) as? SelectionValue
|
let value = Int(valueString) as? SelectionValue
|
||||||
|
|
|
@ -24,6 +24,7 @@ extension SecureField: ViewDeferredToRenderer where Label == Text {
|
||||||
"type": "password",
|
"type": "password",
|
||||||
"value": proxy.textBinding.wrappedValue,
|
"value": proxy.textBinding.wrappedValue,
|
||||||
"placeholder": proxy.label.rawText,
|
"placeholder": proxy.label.rawText,
|
||||||
|
"class": "_tokamak-securefield",
|
||||||
], listeners: [
|
], listeners: [
|
||||||
"keypress": { event in if event.key == "Enter" { proxy.onCommit() } },
|
"keypress": { event in if event.key == "Enter" { proxy.onCommit() } },
|
||||||
"input": { event in
|
"input": { event in
|
||||||
|
|
|
@ -17,18 +17,29 @@
|
||||||
|
|
||||||
import TokamakCore
|
import TokamakCore
|
||||||
|
|
||||||
func css(for style: TextFieldStyle) -> String {
|
|
||||||
if style is PlainTextFieldStyle {
|
|
||||||
return """
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
"""
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TextField: ViewDeferredToRenderer where Label == Text {
|
extension TextField: ViewDeferredToRenderer where Label == Text {
|
||||||
|
func css(for style: TextFieldStyle) -> String {
|
||||||
|
if style is PlainTextFieldStyle {
|
||||||
|
return """
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
"""
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func className(for style: TextFieldStyle) -> String {
|
||||||
|
switch style {
|
||||||
|
case is DefaultTextFieldStyle:
|
||||||
|
return "_tokamak-textfield-default"
|
||||||
|
case is RoundedBorderTextFieldStyle:
|
||||||
|
return "_tokamak-textfield-roundedborder"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var deferredBody: AnyView {
|
public var deferredBody: AnyView {
|
||||||
let proxy = _TextFieldProxy(self)
|
let proxy = _TextFieldProxy(self)
|
||||||
|
|
||||||
|
@ -37,6 +48,7 @@ extension TextField: ViewDeferredToRenderer where Label == Text {
|
||||||
"value": proxy.textBinding.wrappedValue,
|
"value": proxy.textBinding.wrappedValue,
|
||||||
"placeholder": proxy.label.rawText,
|
"placeholder": proxy.label.rawText,
|
||||||
"style": css(for: proxy.textFieldStyle),
|
"style": css(for: proxy.textFieldStyle),
|
||||||
|
"class": className(for: proxy.textFieldStyle),
|
||||||
], listeners: [
|
], listeners: [
|
||||||
"focus": { _ in proxy.onEditingChanged(true) },
|
"focus": { _ in proxy.onEditingChanged(true) },
|
||||||
"blur": { _ in proxy.onEditingChanged(false) },
|
"blur": { _ in proxy.onEditingChanged(false) },
|
||||||
|
|
|
@ -36,20 +36,20 @@ public struct ColorDemo: View {
|
||||||
case rgb, hsb
|
case rgb, hsb
|
||||||
}
|
}
|
||||||
|
|
||||||
let colors: [Color] = [
|
let colors: [(String, Color)] = [
|
||||||
.clear,
|
("Clear", .clear),
|
||||||
.black,
|
("Black", .black),
|
||||||
.white,
|
("White", .white),
|
||||||
.gray,
|
("Gray", .gray),
|
||||||
.red,
|
("Red", .red),
|
||||||
.green,
|
("Green", .green),
|
||||||
.blue,
|
("Blue", .blue),
|
||||||
.orange,
|
("Orange", .orange),
|
||||||
.yellow,
|
("Yellow", .yellow),
|
||||||
.pink,
|
("Pink", .pink),
|
||||||
.purple,
|
("Purple", .purple),
|
||||||
.primary,
|
("Primary", .primary),
|
||||||
.secondary,
|
("Secondary", .secondary),
|
||||||
]
|
]
|
||||||
|
|
||||||
@State private var colorForm: ColorForm = .hsb
|
@State private var colorForm: ColorForm = .hsb
|
||||||
|
@ -71,16 +71,16 @@ public struct ColorDemo: View {
|
||||||
.bold()
|
.bold()
|
||||||
.padding()
|
.padding()
|
||||||
.background(color)
|
.background(color)
|
||||||
Text("Accent Color: \(Color.accentColor.description)")
|
Text("Accent Color: \(String(describing: Color.accentColor))")
|
||||||
.bold()
|
.bold()
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.accentColor)
|
.background(Color.accentColor)
|
||||||
ForEach(colors, id: \.self) {
|
ForEach(colors, id: \.0) {
|
||||||
Text($0.description)
|
Text($0.0)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.bold()
|
.bold()
|
||||||
.padding()
|
.padding()
|
||||||
.background($0)
|
.background($0.1)
|
||||||
}
|
}
|
||||||
}.padding(.horizontal)
|
}.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,6 @@ extension _ZIndexModifier: DOMViewModifier {
|
||||||
extension _BackgroundModifier: DOMViewModifier where Background == Color {
|
extension _BackgroundModifier: DOMViewModifier where Background == Color {
|
||||||
public var isOrderDependent: Bool { true }
|
public var isOrderDependent: Bool { true }
|
||||||
public var attributes: [String: String] {
|
public var attributes: [String: String] {
|
||||||
["style": "background-color: \(background.description)"]
|
["style": "background-color: \(background.cssValue(environment))"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,34 @@ public let tokamakStyles = """
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
border-radius: .1em;
|
border-radius: .1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 {
|
||||||
|
background-color: rgb(100, 100, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
public let rootNodeStyles = """
|
public let rootNodeStyles = """
|
||||||
|
|
|
@ -25,7 +25,7 @@ extension _OverlayModifier: DOMViewModifier
|
||||||
return ["style": """
|
return ["style": """
|
||||||
border-style: \(style);
|
border-style: \(style);
|
||||||
border-width: \(overlay.shape.style.lineWidth);
|
border-width: \(overlay.shape.style.lineWidth);
|
||||||
border-color: \(overlay.style.description);
|
border-color: \(overlay.style.cssValue(environment));
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
"""]
|
"""]
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ protocol ShapeAttributes {
|
||||||
extension _StrokedShape: ShapeAttributes {
|
extension _StrokedShape: ShapeAttributes {
|
||||||
func attributes(_ style: ShapeStyle) -> [String: String] {
|
func attributes(_ style: ShapeStyle) -> [String: String] {
|
||||||
if let color = style as? Color {
|
if let color = style as? Color {
|
||||||
return ["style": "stroke: \(color); fill: none;"]
|
return ["style": "stroke: \(color.cssValue(environment)); fill: none;"]
|
||||||
} else {
|
} else {
|
||||||
return ["style": "stroke: black; fill: none;"]
|
return ["style": "stroke: black; fill: none;"]
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,9 @@ extension _ShapeView: ViewDeferredToRenderer {
|
||||||
if let shapeAttributes = shape as? ShapeAttributes {
|
if let shapeAttributes = shape as? ShapeAttributes {
|
||||||
return AnyView(HTML("div", shapeAttributes.attributes(style)) { path })
|
return AnyView(HTML("div", shapeAttributes.attributes(style)) { path })
|
||||||
} else if let color = style as? Color {
|
} else if let color = style as? Color {
|
||||||
return AnyView(HTML("div", ["style": "fill: \(color);"]) { path })
|
return AnyView(HTML("div", ["style": "fill: \(color.cssValue(environment));"]) { path })
|
||||||
} else if let foregroundColor = foregroundColor {
|
} else if let foregroundColor = foregroundColor {
|
||||||
return AnyView(HTML("div", ["style": "fill: \(foregroundColor);"]) { path })
|
return AnyView(HTML("div", ["style": "fill: \(foregroundColor.cssValue(environment));"]) { path })
|
||||||
} else {
|
} else {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,10 @@
|
||||||
|
|
||||||
import TokamakCore
|
import TokamakCore
|
||||||
|
|
||||||
extension Color: CustomStringConvertible {
|
extension Color {
|
||||||
public var description: String {
|
func cssValue(_ environment: EnvironmentValues) -> String {
|
||||||
"rgb(\(red * 255), \(green * 255), \(blue * 255), \(opacity * 255))"
|
let rgba = _evaluate(environment)
|
||||||
|
return "rgba(\(rgba.red * 255), \(rgba.green * 255), \(rgba.blue * 255), \(rgba.opacity))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,12 @@ extension SidebarListStyle: ListStyleDeferredToRenderer {
|
||||||
AnyView(content
|
AnyView(content
|
||||||
.padding(.all)
|
.padding(.all)
|
||||||
.padding(.leading, 20)
|
.padding(.leading, 20)
|
||||||
.background(Color(0xF2F2F7))
|
.background(Color._withScheme {
|
||||||
|
switch $0 {
|
||||||
|
case .light: return Color(0xF2F2F7)
|
||||||
|
case .dark: return Color(.sRGB, red: 45 / 255, green: 43 / 255, blue: 48 / 255)
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,12 @@ extension Divider: AnyHTML {
|
||||||
width: 100%; height: 0; margin: 0;
|
width: 100%; height: 0; margin: 0;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
border-bottom: 1px solid \(Color._withScheme {
|
||||||
|
switch $0 {
|
||||||
|
case .light: return .init(.sRGB, white: 0, opacity: 0.2)
|
||||||
|
case .dark: return .init(.sRGB, white: 1, opacity: 0.2)
|
||||||
|
}
|
||||||
|
}.cssValue(environment));
|
||||||
border-left: none;
|
border-left: none;
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
|
|
|
@ -151,13 +151,13 @@ extension Text: AnyHTML {
|
||||||
}
|
}
|
||||||
}.inlineStyles ?? "")
|
}.inlineStyles ?? "")
|
||||||
\(font == nil ? "font-family: \(Font.Design.default.description);" : "")
|
\(font == nil ? "font-family: \(Font.Design.default.description);" : "")
|
||||||
color: \(color?.description ?? "inherit");
|
color: \((color ?? .primary).cssValue(environment));
|
||||||
font-style: \(italic ? "italic" : "normal");
|
font-style: \(italic ? "italic" : "normal");
|
||||||
font-weight: \(weight?.value ?? font?._weight.value ?? 400);
|
font-weight: \(weight?.value ?? font?._weight.value ?? 400);
|
||||||
letter-spacing: \(kerning);
|
letter-spacing: \(kerning);
|
||||||
vertical-align: \(baseline == nil ? "baseline" : "\(baseline!)em");
|
vertical-align: \(baseline == nil ? "baseline" : "\(baseline!)em");
|
||||||
text-decoration: \(textDecoration);
|
text-decoration: \(textDecoration);
|
||||||
text-decoration-color: \(strikethrough?.1?.description ?? underline?.1?.description
|
text-decoration-color: \(strikethrough?.1?.cssValue(environment) ?? underline?.1?.cssValue(environment)
|
||||||
?? "inherit")
|
?? "inherit")
|
||||||
""",
|
""",
|
||||||
"class": isRedacted ? "_tokamak-text-redacted" : "",
|
"class": isRedacted ? "_tokamak-text-redacted" : "",
|
||||||
|
|
|
@ -17,7 +17,8 @@ import XCTest
|
||||||
|
|
||||||
final class ColorTests: XCTestCase {
|
final class ColorTests: XCTestCase {
|
||||||
func testHexColors() {
|
func testHexColors() {
|
||||||
guard let color = Color(hex: "#FF00FF") else {
|
let env = EnvironmentValues()
|
||||||
|
guard let color = Color(hex: "#FF00FF")?._evaluate(env) else {
|
||||||
XCTFail("Hexadecimal decoding failed")
|
XCTFail("Hexadecimal decoding failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -28,11 +29,11 @@ final class ColorTests: XCTestCase {
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
color,
|
color,
|
||||||
Color(hex: "FF00FF"),
|
Color(hex: "FF00FF")?._evaluate(env),
|
||||||
"The '#' before a hex code produced a different output than without it"
|
"The '#' before a hex code produced a different output than without it"
|
||||||
)
|
)
|
||||||
|
|
||||||
guard let red = Color(hex: "#FF0000") else {
|
guard let red = Color(hex: "#FF0000")?._evaluate(env) else {
|
||||||
XCTFail("Hexadecimal decoding failed")
|
XCTFail("Hexadecimal decoding failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -41,7 +42,7 @@ final class ColorTests: XCTestCase {
|
||||||
XCTAssertEqual(red.green, 0)
|
XCTAssertEqual(red.green, 0)
|
||||||
XCTAssertEqual(red.blue, 0)
|
XCTAssertEqual(red.blue, 0)
|
||||||
|
|
||||||
guard let green = Color(hex: "#00FF00") else {
|
guard let green = Color(hex: "#00FF00")?._evaluate(env) else {
|
||||||
XCTFail("Hexadecimal decoding failed")
|
XCTFail("Hexadecimal decoding failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -50,7 +51,7 @@ final class ColorTests: XCTestCase {
|
||||||
XCTAssertEqual(green.green, 1)
|
XCTAssertEqual(green.green, 1)
|
||||||
XCTAssertEqual(green.blue, 0)
|
XCTAssertEqual(green.blue, 0)
|
||||||
|
|
||||||
guard let blue = Color(hex: "#0000FF") else {
|
guard let blue = Color(hex: "#0000FF")?._evaluate(env) else {
|
||||||
XCTFail("Hexadecimal decoding failed")
|
XCTFail("Hexadecimal decoding failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue