Skip to content

Commit

Permalink
Merge pull request #188 from kiwicom/50-elevation
Browse files Browse the repository at this point in the history
Add elevation levels
  • Loading branch information
sjavora authored Jun 24, 2022
2 parents f3b2466 + ebbd7af commit 337f4ba
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 4 deletions.
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 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.
Binary file modified Snapshots/iOS15_iPhone_13/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 Snapshots/iOS15_iPhone_13/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.
2 changes: 1 addition & 1 deletion Sources/Orbit/Components/Switch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public struct Switch: View {
Circle()
.frame(width: circleDiameter, height: circleDiameter)
.foregroundColor(.whiteNormal)
.shadow(color: Self.shadowColor.opacity(isEnabled ? 1 : 0), radius: 2.5, y: 1.5)
.elevation(isEnabled ? .custom(opacity: 0.25, radius: 1.4, y: 1.2) : nil)
.overlay(
Circle()
.strokeBorder(Self.borderColor, lineWidth: BorderWidth.hairline)
Expand Down
3 changes: 1 addition & 2 deletions Sources/Orbit/Components/Tabs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ public struct Tabs<Content: View>: View {
.frame(height: underlineHeight * sizeCategory.ratio)
}
.clipShape(RoundedRectangle(cornerRadius: BorderRadius.default - 1))
.shadow(color: .black.opacity(0.06), radius: 1, x: 0, y: 1)
.shadow(color: .black.opacity(0.08), radius: 4, x: 0, y: 3)
.elevation(.level1, prerender: false)
.padding(.xxxSmall)
}

Expand Down
202 changes: 202 additions & 0 deletions Sources/Orbit/Foundation/Elevations/Elevation.swift
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))
}
}
36 changes: 36 additions & 0 deletions Sources/Orbit/Orbit.docc/Foundation/Elevation.md
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)
```
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

### Elevation

- <doc:Elevation>

### Icons

### Illustrations
Expand Down
1 change: 1 addition & 0 deletions Sources/Orbit/Orbit.docc/Uncategorized.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Supporting types and components under development.

- ``IsExpandedKey``
- ``IsFadeInKey``
- ``IsElevationEnabledKey``

### Other

Expand Down
17 changes: 17 additions & 0 deletions Sources/Orbit/Support/Environment Keys/IsElevationEnabledKey.swift
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 }
}
}
2 changes: 1 addition & 1 deletion Sources/Orbit/Support/Environment Keys/IsExpandedKey.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SwiftUI

/// Envoronment key for driving expanded animation simultaneously with view transition.
/// Environment key for driving expanded animation simultaneously with view transition.
public struct IsExpandedKey: EnvironmentKey {
public static var defaultValue: Bool = false
}
Expand Down

0 comments on commit 337f4ba

Please sign in to comment.