-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #188 from kiwicom/50-elevation
Add elevation levels
- Loading branch information
Showing
14 changed files
with
261 additions
and
4 deletions.
There are no files selected for viewing
Binary file modified
BIN
-2.98 KB
(84%)
Snapshots/iOS15_iPad_(9th_generation)/SwitchTests/testSwitches.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+9.24 KB
(110%)
Snapshots/iOS15_iPad_(9th_generation)/TabsTests/testTabs.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
-2.98 KB
(84%)
Snapshots/iOS15_iPhone_SE_(1st_generation)/SwitchTests/testSwitches.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+8.29 KB
(110%)
Snapshots/iOS15_iPhone_SE_(1st_generation)/TabsTests/testTabs.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import SwiftUI | ||
|
||
/// Use elevation to bring content closer to users. | ||
/// | ||
/// Elevation levels with higher numbers are usually visually closer to the user. | ||
public enum Elevation { | ||
case level1 | ||
case level2 | ||
case level3 | ||
case level4 | ||
case custom(opacity: Double, radius: CGFloat, x: CGFloat = 0, y: CGFloat = 0, padding: CGFloat? = nil) | ||
} | ||
|
||
public extension View { | ||
|
||
/// Elevates the view to make it more prominent. | ||
func elevation(_ level: Elevation?) -> some View { | ||
elevation(level, prerender: true) | ||
} | ||
} | ||
|
||
extension View { | ||
|
||
func elevation(_ level: Elevation?, prerender: Bool) -> some View { | ||
modifier(ElevationModifier(level: level, isPrerendered: prerender)) | ||
} | ||
} | ||
|
||
struct ElevationModifier: ViewModifier { | ||
|
||
@Environment(\.isElevationEnabled) var isElevationEnabled | ||
let level: Elevation? | ||
let isPrerendered: Bool | ||
let shadowColor = Color(red: 79 / 255, green: 94 / 255, blue: 113 / 255) | ||
|
||
func body(content: Content) -> some View { | ||
if isElevationEnabled { | ||
if isPrerendered { | ||
content | ||
.background(prerenderedShadow(content: content)) | ||
} else { | ||
liveShadow(content: content) | ||
} | ||
} else { | ||
content | ||
} | ||
} | ||
|
||
@ViewBuilder func prerenderedShadow(content: Content) -> some View { | ||
switch level { | ||
case .level1: | ||
content | ||
.shadow(color: shadowColor.opacity(0.32), radius: 2.4, y: 1.9) | ||
.padding(.small) | ||
.drawingGroup(colorMode: .extendedLinear) | ||
case .level2: | ||
content | ||
.shadow(color: shadowColor.opacity(0.26), radius: 3, y: 2.1) | ||
.shadow(color: shadowColor.opacity(0.1), radius: 10, y: 9) | ||
.padding(.large) | ||
.drawingGroup(colorMode: .extendedLinear) | ||
case .level3: | ||
content | ||
.shadow(color: shadowColor.opacity(0.28), radius: 2.8, y: 2.4) | ||
.shadow(color: shadowColor.opacity(0.16), radius: 12, y: 12) | ||
.padding(.xLarge) | ||
.drawingGroup(colorMode: .extendedLinear) | ||
case .level4: | ||
content | ||
.shadow(color: shadowColor.opacity(0.3), radius: 3.2, y: 2.7) | ||
.shadow(color: shadowColor.opacity(0.17), radius: 18, y: 18) | ||
.padding(.xxLarge) | ||
.drawingGroup(colorMode: .extendedLinear) | ||
case .custom(let opacity, let radius, let x, let y, let padding): | ||
content | ||
.shadow(color: shadowColor.opacity(opacity), radius: radius, x: x, y: y) | ||
.padding(padding ?? (radius + y)) | ||
.drawingGroup(colorMode: .extendedLinear) | ||
case nil: | ||
content | ||
} | ||
} | ||
|
||
@ViewBuilder func liveShadow(content: Content) -> some View { | ||
switch level { | ||
case .level1: | ||
content | ||
.shadow(color: shadowColor.opacity(0.12), radius: 1, y: 1) | ||
.shadow(color: shadowColor.opacity(0.11), radius: 2, y: 2) | ||
.shadow(color: shadowColor.opacity(0.10), radius: 4, y: 4) | ||
case .level2: | ||
content | ||
.shadow(color: shadowColor.opacity(0.12), radius: 2, y: 2) | ||
.shadow(color: shadowColor.opacity(0.11), radius: 4, y: 4) | ||
.shadow(color: shadowColor.opacity(0.10), radius: 8, y: 8) | ||
case .level3: | ||
content | ||
.shadow(color: shadowColor.opacity(0.12), radius: 2, y: 2) | ||
.shadow(color: shadowColor.opacity(0.11), radius: 4, y: 4) | ||
.shadow(color: shadowColor.opacity(0.10), radius: 8, y: 8) | ||
.shadow(color: shadowColor.opacity(0.06), radius: 16, y: 16) | ||
case .level4: | ||
content | ||
.shadow(color: shadowColor.opacity(0.12), radius: 2, y: 2) | ||
.shadow(color: shadowColor.opacity(0.11), radius: 4, y: 4) | ||
.shadow(color: shadowColor.opacity(0.10), radius: 8, y: 8) | ||
.shadow(color: shadowColor.opacity(0.09), radius: 16, y: 16) | ||
.shadow(color: shadowColor.opacity(0.06), radius: 32, y: 32) | ||
case .custom(let opacity, let radius, let x, let y, _): | ||
content | ||
.shadow(color: shadowColor.opacity(opacity), radius: radius, x: x, y: y) | ||
case nil: | ||
content | ||
} | ||
} | ||
} | ||
|
||
struct ElevationPreviews: PreviewProvider { | ||
|
||
static var previews: some View { | ||
prerendered | ||
.previewLayout(.sizeThatFits) | ||
snapshot | ||
.previewLayout(.sizeThatFits) | ||
compositingGroup | ||
.previewLayout(.sizeThatFits) | ||
} | ||
|
||
static var snapshot: some View { | ||
PreviewWrapper { | ||
live | ||
} | ||
} | ||
|
||
static var prerendered: some View { | ||
HStack(spacing: 90) { | ||
squircle("Level 4") | ||
.elevation(.level4) | ||
squircle("Level 3") | ||
.elevation(.level3) | ||
squircle("Level 2") | ||
.elevation(.level2) | ||
squircle("Level 1") | ||
.elevation(.level1) | ||
} | ||
.padding(40) | ||
.fixedSize() | ||
.previewDisplayName("Prerendered") | ||
} | ||
|
||
static var live: some View { | ||
HStack(spacing: 90) { | ||
squircle("Level 4") | ||
.elevation(.level4, prerender: false) | ||
squircle("Level 3") | ||
.elevation(.level3, prerender: false) | ||
squircle("Level 2") | ||
.elevation(.level2, prerender: false) | ||
squircle("Level 1") | ||
.elevation(.level1, prerender: false) | ||
} | ||
.padding(40) | ||
.fixedSize() | ||
.previewDisplayName("Live") | ||
} | ||
|
||
@ViewBuilder static var compositingGroup: some View { | ||
ZStack { | ||
squircle("Level 3") | ||
.elevation(.level3) | ||
.offset(x: -20, y: -20) | ||
squircle("Level 3") | ||
.elevation(.level3) | ||
.offset(x: 20, y: 20) | ||
} | ||
.padding(80) | ||
.background(Gradient.bundleTop.background.opacity(0.05)) | ||
.environment(\.isElevationEnabled, false) | ||
.compositingGroup() | ||
.elevation(.level3) | ||
.previewDisplayName("With Compositing Group") | ||
|
||
ZStack { | ||
squircle("Level 3") | ||
.elevation(.level3) | ||
.offset(x: -20, y: -20) | ||
squircle("Level 3") | ||
.elevation(.level3) | ||
.offset(x: 20, y: 20) | ||
} | ||
.padding(80) | ||
.background(Gradient.bundleTop.background.opacity(0.05)) | ||
.previewDisplayName("Without Compositing Group (Default)") | ||
} | ||
|
||
static func squircle(_ title: String) -> some View { | ||
Heading(title, style: .title1) | ||
.frame(width: 240, height: 260) | ||
.background(Color.white) | ||
.clipShape(RoundedRectangle(cornerRadius: BorderRadius.large)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Elevation | ||
|
||
Use elevation to bring content closer to users. | ||
|
||
## Overview | ||
|
||
Elevation makes a view stand out by adding a shadow. | ||
|
||
Note: [Orbit definition](https://orbit.kiwi/foundation/elevation/) | ||
|
||
## Adding elevation to a view | ||
|
||
Use the `View.elevation(_:)` modifier to add elevation to a view: | ||
|
||
```swift | ||
Color.red | ||
.frame(width: 100, height: 100) | ||
.elevation(.level3) | ||
``` | ||
|
||
The higher the elevation level, the more the view stands out. | ||
|
||
Multiple views can be elevated at once by using this modifier on a container view. | ||
Either for performance reasons, but also to be able to use `compositingGroup()` modifier | ||
to decide whether the elevation will be applied on each view separately or on their merged composition. | ||
|
||
## Suppressing existing elevation | ||
|
||
Elevation in child views can be optionally disabled by using ``IsElevationEnabledKey`` environment key, | ||
typically in order to disable elevation that is present by default on some Orbit components, | ||
to be able to apply the elevation for a subset of components at once. | ||
|
||
```swift | ||
Switch(...) | ||
.environment(\.isElevationEnabled, false) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,8 @@ | |
|
||
### Elevation | ||
|
||
- <doc:Elevation> | ||
|
||
### Icons | ||
|
||
### Illustrations | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
Sources/Orbit/Support/Environment Keys/IsElevationEnabledKey.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import SwiftUI | ||
|
||
/// Environment key for disabling built-in elevation in Orbit components. | ||
public struct IsElevationEnabledKey: EnvironmentKey { | ||
public static var defaultValue: Bool = true | ||
} | ||
|
||
public extension EnvironmentValues { | ||
|
||
/// Indicates whether an Orbit component should use its built-in elevation. | ||
/// | ||
/// Set this to `false` if you want to provide your own elevation. | ||
var isElevationEnabled: Bool { | ||
get { self[IsElevationEnabledKey.self] } | ||
set { self[IsElevationEnabledKey.self] = newValue } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters