Orange-OpenSource / ouds-ios

A SwiftUI components library with code examples for Orange Unified Design System
https://ios.unified-design-system.orange.com/
MIT License
5 stars 1 forks source link

Tokens: Elevation #32

Open julien-deramond opened 2 months ago

julien-deramond commented 2 months ago

Description

The aim of this issue is to study and implement the elevation tokens taking into account the cohesive multi-platform approach, the customization by libraries inheriting OUDS iOS, but also what we're using for OUDS iOS: Swift UI, etc.

Reminder: there will be in the end 3 layers of tokens:

Tokens: Elevation

Raw primitive values

Z-index

Token name Token value
elevation-z-index-0 0
elevation-z-index--9999 -9999
elevation-z-index-1000 1000
elevation-z-index-1010 1010
elevation-z-index-1020 1020
elevation-z-index-1030 1030
elevation-z-index-1035 1035
elevation-z-index-1038 1038
elevation-z-index-1040 1040
elevation-z-index-1045 1045
elevation-z-index-1050 1050
elevation-z-index-1060 1060
elevation-z-index-1070 1070
elevation-z-index-1080 1080
elevation-z-index-1090 1090

X

Token name Token value
elevation-x-0 0

Y

Token name Token value
elevation-y-0 0
elevation-y-100 1
elevation-y-200 2
elevation-y-300 4
elevation-y-400 8
elevation-y-500 12
elevation-y-600 20

Blur

Token name Token value
elevation-blur-0 0
elevation-blur-100 1
elevation-blur-200 2
elevation-blur-300 3
elevation-blur-400 4
elevation-blur-500 8
elevation-blur-600 12
elevation-blur-700 20

Spread

Token name Token value
elevation-spread-n100 -1
elevation-spread-n200 -2
elevation-spread-n300 -4
elevation-spread-n400 -8
elevation-spread-0 0
elevation-spread-300 3

Box shadow (Composite)

[!CAUTION] Figma doesn't accept composite tokens

Token name Token value
elevation-bottom-0 box-shadow: 0 0px 0px 0 rgba({core.color.transparent.black.0});
elevation-bottom-1-100 box-shadow: 0 1px 2px 0 rgba({core.color.transparent.black.100});
elevation-bottom-1-200 box-shadow: 0 1px 2px 0 rgba({core.color.transparent.black.200});
elevation-bottom-1-300 box-shadow: 0 1px 2px 0 rgba({core.color.transparent.black.300});
elevation-bottom-1-400 box-shadow: 0 1px 2px 0 rgba({core.color.transparent.black.400});
elevation-bottom-1-500 box-shadow: 0 1px 2px 0 rgba({core.color.transparent.black.500});
elevation-bottom-1-600 box-shadow: 0 1px 2px 0 rgba({core.color.transparent.black.600});
elevation-bottom-2-100 box-shadow: 0 2px 3px 0 rgba({core.color.transparent.black.100});
elevation-bottom-2-200 box-shadow: 0 2px 3px 0 rgba({core.color.transparent.black.200});
elevation-bottom-2-300 box-shadow: 0 2px 3px 0 rgba({core.color.transparent.black.300});
elevation-bottom-2-400 box-shadow: 0 2px 3px 0 rgba({core.color.transparent.black.400});
elevation-bottom-2-500 box-shadow: 0 2px 3px 0 rgba({core.color.transparent.black.500});
elevation-bottom-2-600 box-shadow: 0 2px 3px 0 rgba({core.color.transparent.black.600});
elevation-bottom-3-100 box-shadow: 0 4px 4px -1px rgba({core.color.transparent.black.100});
elevation-bottom-3-200 box-shadow: 0 4px 4px -1px rgba({core.color.transparent.black.200});
elevation-bottom-3-300 box-shadow: 0 4px 4px -1px rgba({core.color.transparent.black.300});
elevation-bottom-3-400 box-shadow: 0 4px 4px -1px rgba({core.color.transparent.black.400});
elevation-bottom-3-500 box-shadow: 0 4px 4px -1px rgba({core.color.transparent.black.500});
elevation-bottom-3-600 box-shadow: 0 4px 4px -1px rgba({core.color.transparent.black.600});
elevation-bottom-4-100 box-shadow: 0 8px 8px -2px rgba({core.color.transparent.black.100});
elevation-bottom-4-200 box-shadow: 0 8px 8px -2px rgba({core.color.transparent.black.200});
elevation-bottom-4-300 box-shadow: 0 8px 8px -2px rgba({core.color.transparent.black.300});
elevation-bottom-4-400 box-shadow: 0 8px 8px -2px rgba({core.color.transparent.black.400});
elevation-bottom-4-500 box-shadow: 0 8px 8px -2px rgba({core.color.transparent.black.500});
elevation-bottom-4-600 box-shadow: 0 8px 8px -2px rgba({core.color.transparent.black.600});
elevation-bottom-5-100 box-shadow: 0 12px 12px -4px rgba({core.color.transparent.black.100});
elevation-bottom-5-200 box-shadow: 0 12px 12px -4px rgba({core.color.transparent.black.200});
elevation-bottom-5-300 box-shadow: 0 12px 12px -4px rgba({core.color.transparent.black.300});
elevation-bottom-5-400 box-shadow: 0 12px 12px -4px rgba({core.color.transparent.black.400});
elevation-bottom-5-500 box-shadow: 0 12px 12px -4px rgba({core.color.transparent.black.500});
elevation-bottom-5-600 box-shadow: 0 12px 12px -4px rgba({core.color.transparent.black.600});
elevation-bottom-6-100 box-shadow: 0 20px 20px -8px rgba({core.color.transparent.black.100});
elevation-bottom-6-200 box-shadow: 0 20px 20px -8px rgba({core.color.transparent.black.200});
elevation-bottom-6-300 box-shadow: 0 20px 20px -8px rgba({core.color.transparent.black.300});
elevation-bottom-6-400 box-shadow: 0 20px 20px -8px rgba({core.color.transparent.black.400});
elevation-bottom-6-500 box-shadow: 0 20px 20px -8px rgba({core.color.transparent.black.500});
elevation-bottom-6-600 box-shadow: 0 20px 20px -8px rgba({core.color.transparent.black.600});

Semantic applications

Z-index

Token name Token value
elevation-z-index-deep elevation-zindex--9999
elevation-z-index-default elevation-zindex-0
elevation-z-index-dropdown elevation-zindex-1000
elevation-z-index-sticky elevation-zindex-1020
elevation-z-index-fixed elevation-zindex-1030
elevation-z-index-back-to-top elevation-zindex-1035
elevation-z-index-spinner elevation-zindex-1038
elevation-z-index-offcanvas-backdrop elevation-zindex-1040
elevation-z-index-offcanvas elevation-zindex-1045
elevation-z-index-modal-backdrop elevation-zindex-1050
elevation-z-index-modal elevation-zindex-1060
elevation-z-index-popover elevation-zindex-1070
elevation-z-index-tooltip elevation-zindex-1080
elevation-z-index-toast elevation-zindex-1090

X

Token name Token value
elevation-x-none elevation-x-0
elevation-x-raised elevation-x-0
elevation-x-drag elevation-x-0
elevation-x-overlay-default elevation-x-0
elevation-x-overlay-emphasized elevation-x-0
elevation-x-sticky-default elevation-x-0
elevation-x-sticky-emphasized elevation-x-0
elevation-x-sticky-navigation-scrolled elevation-x-0
elevation-x-focus elevation-x-0

Y

Token name Token value
elevation-y-none elevation-y-0
elevation-y-raised elevation-y-100
elevation-y-drag elevation-y-300
elevation-y-overlay-default elevation-y-200
elevation-y-overlay-emphasized elevation-y-500
elevation-y-sticky-default elevation-y-300
elevation-y-sticky-emphasized elevation-y-300
elevation-y-sticky-navigation-scrolled elevation-y-300
elevation-y-focus elevation-y-0

Blur

Token name Token value
elevation-blur-none elevation-blur-0
elevation-blur-raised elevation-blur-200
elevation-blur-drag elevation-blur-400
elevation-blur-overlay-default elevation-blur-300
elevation-blur-overlay-emphasized elevation-blur-600
elevation-blur-sticky-default elevation-blur-400
elevation-blur-sticky-emphasized elevation-blur-400
elevation-blur-sticky-navigation-scrolled elevation-blur-400
elevation-blur-focus elevation-blur-0

Spread

Token name Token value
elevation-spread-none elevation-spread-0
elevation-spread-raised elevation-spread-0
elevation-spread-drag elevation-spread-n100
elevation-spread-overlay-default elevation-spread-n100
elevation-spread-overlay-emphasized elevation-spread-n300
elevation-spread-sticky-default elevation-spread-n100
elevation-spread-sticky-emphasized elevation-spread-n100
elevation-spread-sticky-navigation-scrolled elevation-spread-n100
elevation-spread-focus elevation-spread-300

Color

Token name Token value (light) Token value (inverse) Token value (dark)
elevation-color-none color-transparent-black-0 color-transparent-black-0 color-transparent-black-0
elevation-color-raised color-transparent-black-500 color-transparent-black-500 color-transparent-black-500
elevation-color-drag color-transparent-black-600 color-transparent-black-600 color-transparent-black-600
elevation-color-overlay-default color-transparent-black-400 color-transparent-black-400 color-transparent-black-400
elevation-color-overlay-emphasized color-transparent-black-300 color-transparent-black-300 color-transparent-black-300
elevation-color-sticky-default color-transparent-black-300 color-transparent-black-300 color-transparent-black-300
elevation-color-sticky-emphasized color-transparent-black-300 color-transparent-black-300 color-transparent-black-300
elevation-color-sticky-navigation-scrolled color-transparent-black-300 color-transparent-black-300 color-transparent-black-300
elevation-color-focus color-transparent-white-900 color-transparent-white-900 color-transparent-white-900

Box shadow (Composite)

[!CAUTION] Figma doesn't accept composite tokens

Token name Token value (light) Token value (inverse) Token value (dark)
elevation-none elevation-bottom-0 elevation-bottom-0 elevation-bottom-0
elevation-raised elevation-bottom-1-500 elevation-bottom-1-500 elevation-bottom-1-500
elevation-drag elevation-bottom-3-500 elevation-bottom-3-500 elevation-bottom-3-500
elevation-overlay-default elevation-bottom-2-400 elevation-bottom-2-400 elevation-bottom-2-400
elevation-overlay-emphasized elevation-bottom-5-300 elevation-bottom-5-300 elevation-bottom-5-300
elevation-sticky-default elevation-bottom-3-300 elevation-bottom-3-300 elevation-bottom-3-300
elevation-sticky-emphasized elevation-bottom-3-300 elevation-bottom-3-300 elevation-bottom-3-300
elevation-sticky-navigation-scrolled elevation-bottom-3-300 elevation-bottom-3-300 elevation-bottom-3-300
elevation-focus box-shadow: 0 0px 0px 3px rgba({sys-color-transparent-black-900}); box-shadow: 0 0px 0px 3px rgba({sys-color-transparent-white-900}); box-shadow: 0 0px 0px 3px rgba({sys-color-transparent-white-900});

Study

Technical details

TODO

pylapp commented 1 month ago

For information, a first draft is available in the implementation of #33 due to the need to test the architect with available tokens. Of course any improvements, fixes and implementations after the merge of #33 must be linked to this issue to keep consistency.

In addition, "shared" raw tokens defined in OUDSTheme module (common to all themes) are not customisable as is in OUDS iOS and should not supposed to be because they define raw types and true values for everything, like a kind of source of truth for everyone. However any theme can define its own raw tokens and assign them to semantic tokens. Of course raw tokens values can be defined thanks to generated Swift code, if we have any efficient and relevant tools suite.

What should be done for this issue:

pylapp commented 1 month ago

@julien-deramond is "Box shadow (Composite)" a raw token for elevation too? Is so, what does box-shadow: 0 1px 2px 0 rgba({core.color.transparent.black.100}); mean?

About the colors, we agreed with Android side to put the "inverse" colors in a dedicaed "Inverse theme".

pylapp commented 1 month ago

@julien-deramond FYI some raw tokens as values of semantic tokens are missing: zindex-1035, zindex-1038, zindex-1045. I may remember they were listed in design team white board, so I implement them. Maybe the issue should be updated.

julien-deramond commented 1 month ago

@julien-deramond FYI some raw tokens as values of semantic tokens are missing: zindex-1035, zindex-1038, zindex-1045. I may remember they were listed in design team white board, so I implement them. Maybe the issue should be updated.

Good catch! I've updated the description with the missing values in all repositories. You can resolve your comment and mine.

julien-deramond commented 1 month ago

About the colors, we agreed with Android side to put the "inverse" colors in a dedicaed "Inverse theme".

Be careful, the color definition/specification is not defined/finalized yet. We might have only the light and dark modes, and the "inverse" being the contextual dark mode.

@julien-deramond is "Box shadow (Composite)" a raw token for elevation too? Is so, what does box-shadow: 0 1px 2px 0 rgba({core.color.transparent.black.100}); mean?

It's a token that is a group of raw values (and sometimes associated with a cascade of raw tokens). Its value would be 0 1px 2px 0 rgba({core.color.transparent.black.100}); (which is not correct as you wouldn't have rgba which is already in core.color.transparent.black.100 btw). In the web world, it would mean something like that, for instance, https://codepen.io/julien-deramond/pen/jOjwywZ:

// Raw

$color-black: #000;
$opacity-100: 0.04;
$color-transparent-black-100: rgba($color-black , $opacity-100);
$elevation-box-shadow-bottom-1-100: 0 1px 2px 0 $color-transparent-black-100;

// Semantic

$elevation-box-shadow-something: $elevation-box-shadow-bottom-1-100;

.example-element {
  box-shadow: $elevation-box-shadow-something;
}

On your side, it might be handled differently.

pylapp commented 1 month ago

Ok, but I would like to know what are the 0 1px 2px 0 values in your raw token. I needed to understand what they are and how they should be used so as to define this token in iOS side.

julien-deramond commented 1 month ago

Ok, but I would like to know what are the 0 1px 2px 0 values in your raw token. I needed to understand what they are and how they should be used so as to define this token in iOS side.

It follows the CSS syntax: https://developer.mozilla.org/fr/docs/Web/CSS/box-shadow#syntaxe (in French)

pylapp commented 1 month ago

What should be the token value of the raw token "elevation-box-shadow-bottom-0"? We cannot guess what must be X, Y, blur, spread and colors.

pylapp commented 1 month ago

In order to test our CI/CD pipeline velocity, avoid to have stale branches, have a fresh code abse for documentation and test our production build (for a fake 0.0.0 version), a first version of the tokens has been merged as is, but the issue is not closed because there are some topics to deal about these elevation tokens.

1) elevation-box-shadow-bottom-0

What should be its core values? We need to know what must be its X, Y, blur, spread and color values. We assume today the "inverse" mode is considered as a dedicated theme with the same value for the light and dark colors.

cc @B3nz01d @mccart77 @ludovic35

pylapp commented 3 weeks ago

FYI first version delivered with commit bd373b48aee005d52e0aca69343573cca0e386eb and released before version 0.1.0

pylapp commented 1 week ago

FYI after a meeting today this token will be updated in the design team source of truth with values to apply for the token elevation-box-shadow-bottom-0.

pylapp commented 6 days ago

Some notes after our last meeting of this afternoon bellow.

Summary

First, "composite tokens" are defined in the design team source of truth but won't be exported and processed as is through the style dictionary tool.

So, until the design tool is ready to export such composite tokens, we should deal with values exported in another fashion.

Design tool for elevations is based on notions of blur and spread, however we managed radius values in SwiftUI and not blur and spread

In addition we have today implemented composite semantic tokens, pointing to composite raw tokens, using raw values, e.g.:

// Composite semantic token "elevationBoxShadowRaisedLight" pointing to composite raw token "elevationBoxShadowBottom_1_500"
public override var elevationBoxShadowRaisedLight: ElevationBoxShadowSemanticToken { ElevationRawTokens.elevationBoxShadowBottom_1_500 }

// Composite raw token "elevationBoxShadowBottom_1_500" using raw values for x, Y, blur, spread and color, even if it does not make sense with SwftUI API
public static let elevationBoxShadowBottom_1_500 = ElevationBoxShadowRawToken(x: 0, y: 1, blur: 2, spread: 0, color: ColorRawTokens.colorTransparentBlack500)

// Definition of the type of composite raw token for elevation bow shadow
public class ElevationBoxShadowRawToken: NSObject { // For @objc compatibility

    public let x: ElevationRawToken
    public let y: ElevationRawToken
    public let blur: ElevationRawToken
    public let spread: ElevationRawToken
    public let color: ColorRawToken

    // ...
}

So, the issue is to be able to define the suitable elevation effects with the inputs generated by the JSON parser.

Implementations to test

Convert blur and spread from design tool combination to SwiftUI radius

If we get blur and spread values from parser output, we may be able to define the radius for SwiftUI to apply. Then the standard API of SwftUI will be used like:

    .shadow(color:, radius:, x:, y:)

The question is: how can we combine blur and spread to get radius?

Tinker something with SwiftUI

Maybe the following code sample may help, but it seems to be a bit dirty tinkering

func customShadow(blur: CGFloat, spread: CGFloat) -> some View {
    Rectangle()
        .fill(Color.clear)
        .frame(width: 100 + spread * 2, height: 100 + spread * 2)
        .shadow(color: Color.black.opacity(0.5), radius: blur, x: 0, y: 5)
}
/*
About the opacity: of 0.5, it gives the shadow a semi-transparent effect, making it softer and more natural. need to check with design team what this want or need.

About the spread calculation, it is simulated by increasing the size of the overlay rectangle. For example, if we want a spread of 5, the overlay dimensions are increased by 10 (5 on each side).

Warning, the customShadow() method may need to know the dimensions of the parent view frame.
*/

struct ContentView: View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .overlay(customShadow(blur: 10, spread: 5)) // <--- Interesting call
    }
}

/* Where:
- Blur: Controlled by the radius parameter in the shadow modifier.
- Spread: Simulated by adjusting the size of an overlay and applying a shadow to it.
*/

Use Bezier path and CALayer

According to this Stack Overflow thread, it might by possible to extend the CALayer and using a UIBezierPath to define some effect, but this algorithm is defined for Sketch, and there are key differences between their implementation and the one in Figma, Sketch having issues with spread notions

Use el famoso pifometer and check

However this code sample may help with another definition of shadow radius combinations more tight to Figma.

import SwiftUI

func computeShadowRadius(blur: CGFloat, spread: CGFloat) -> CGFloat {
    return spread >= 0 ? blur + spread : blur - abs(spread) // Dirty suggested code be the idea to test is here
}

struct ContentView: View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .shadow(color: Color.black.opacity(0.5), radius: computeShadowRadius(blur: 10, spread: 5), x: 0, y: 5)
    }
}

Actions to process

cc @ludovic35 @julien-deramond

pylapp commented 3 days ago

Still wating for screen shots of design team with a rendering of some simple view and its elevation effect depending to blur and spread values. Did not succeed in finding in the amount of tools where this document is.

pylapp commented 3 days ago
Tokens workshop 27_06
pylapp commented 2 days ago

Find attached a ZIP archive containing the Swift demo code and its Git patch, and also 2 screenshots (iPhone and iPad) showing the rendering of the elevation effects.

elevation effects tests.zip