design-tokens / community-group

This is the official DTCG repository for the design tokens specification.
https://tr.designtokens.org
Other
1.55k stars 63 forks source link

Types for CSS Keywords #177

Open kevinmpowell opened 2 years ago

kevinmpowell commented 2 years ago

Do we need types for CSS Keywords that aren't strings: like text-align values: left, right, center etc.

romainmenke commented 2 years ago

The CSS part is maybe a bit misleading. Having keywords/enums for certain effects, values, ... is common in all contexts.

romainmenke commented 2 years ago

https://github.com/design-tokens/community-group/issues/102#issuecomment-1299315744

@chris-dura said :

I've run into a scenario where I want to use the keyword normal for letterSpacing, but the current draft requires that it be a $type: dimension, which needs a px or rem, afaict.

ilikescience commented 1 year ago

Definitely agree that we need a type that passes through a string of text, for all sorts of string-y values, not just CSS keywords. I'd want to broaden the type to something like $string, which can be any valid text string, including escaped characters (see #167 and #171)

romainmenke commented 1 year ago

Definitely agree that we need a type that passes through a string of text, for all sorts of string-y values, not just CSS keywords

I think you misunderstood :) This issue is not about string values, it is about keywords.

Keyword:

.foo {
  text-align: center;
}

String:

.foo {
  content: "string content";
}

But in JSON both need to be stored as strings :

{
  "$value": "center"
}
{
  "$value": "string content"
}

But all languages/platforms have keywords:

{
  "$value": "infinity"
}
const foo = Infinity;
romainmenke commented 1 year ago

This issue is partly resolved by these recent resolutions :

These removed a lot of ambiguity.


For some platforms the fontFamily is still ambiguous. It is impossible to determine if something like "serif" is meant as a string or a keyword.

@font-face {
  font-family: "serif";
  src: ...
}

.foo {
  font-family: "serif";
}

or

.foo {
  font-family: serif;
}

iOS seems to have the same concepts : https://developer.apple.com/documentation/uikit/uifont/3042484-monospacedsystemfont

class func monospacedSystemFont(
    ofSize fontSize: CGFloat,
    weight: UIFont.Weight
) -> UIFont
romainmenke commented 1 year ago

I think the underlying issue is this :

User defined values :

Typically strings, numbers, ...

Platform defined values :

Typically keywords, enums, functions, ...


Some platform defined values have equivalents in all or most platforms. How do we define token values that are intended to use these shared platform defined values.

ilikescience commented 1 year ago

This issue is not about string values, it is about keywords

Yes, sorry, I was using "string" to mean "a piece of text".

I think many use cases can be covered by defining escape sequences (#171). That will help an author in cases like:

{
  "keyword-like-font-name": {
    "$value": "Helvetica",
    "$type": "string"
  },
  "string-like-font-name": {
    "$value": "\"Helvetica\"",
    "$type": "string"
  }
}

(maybe string isn't the right word here, but I don't think keyword is, either. Maybe something like raw or preformatted? see #91 )

Which, when translated into css variables, would result in the following.

:root {
    --keyword-like-font-name: Helvetica;
    --string-like-font-name: "Helvetica";
}

If you'll forgive the pedantry, in this example,

{
  "$value": "infinity"
}
const foo = Infinity;

Infinity is a number, not a keyword (is this a good argument for having a number type in the spec?)


The cases that are not covered by the string type and escaping are where each platform has a different convention for referring to the exact same thing.

Your font example is the most salient one. In CSS, the keyword for "the default system font" is system-ui. In Swift, (please correct me if i'm wrong), you need to use the .system() class with a required size property (eg .system(size: 34)).

Which leads to the question: should translators be required to maintain the mapping these one-to-many keyword values in tokens to the correct platform-specific versions? Or should the spec allow for the author to provide the mapping? Or both?

romainmenke commented 1 year ago

Infinity is a number, not a keyword (is this a good argument for having a number type in the spec?)

That is incorrect. There is no numeric way to express Infinity. It is a keyword that equates to a number when evaluated.

To address the underlying numeric value you need to use a platform specific API. This can be a keyword like Infinity, e pi, ...


Escaping quotes will not work. These are not raw characters.

What if one of the target platforms uses parentheses as string start and end tokens. Or less exotic, a platform that uses only single quotes. This is unusual but not impossible.

"$value": "\"Helvetica\"", makes this specification specific to platforms that can express strings with double quotes.

chris-dura commented 1 year ago

Which leads to the question: should translators be required to maintain the mapping these one-to-many keyword values in tokens to the correct platform-specific versions? Or should the spec allow for the author to provide the mapping? Or both?

Generally, I think "the Tokens spec" should be as agnostic as possible... obviously it's very "CSS-leaning" (as evidenced by the title of this issue)... but "how to represent platform-specific syntax" seems fully the responsibility of the translators/transformers/tools, in my opinion. But, fwiw, the spec already allows for authors to provide custom metadata (via the $extensions) and I've noticed that I've had to make extensive (pun intended) use of it to add custom logic around specific platform outputs. For example, on a css platform I need to convert certain JSON string values to an unquoted keyword, while maybe I have to convert it to something else in a Swift-based output... so, without additions to the spec, if I had to handle this issue, I'd probably do something like this:

{
  "keyword-like-font-name": {
    "$value": "Helvetica",
    "$type": "string"
    "$extensions": {
      "isCssKeyword": true,
    },
  },
}

... then in the transform layer, I'd hook into isCssKeyword to output what I needed to.

romainmenke commented 1 year ago

but "how to represent platform-specific syntax" seems fully the responsibility of the translators/transformers/tools, in my opinion.

This is partly true. It is the responsibility of translation tools to transform to the correct platform specific API. The specification can be agnostic of all the platform specific conversions.

But this specification does need to define the supported/adopted concepts.

This specification does not define system-ui the CSS value, or .system the iOS API. It only needs to define some way to represent the concept.

Translation tools might also provide a public API so that users of those tools can provide fallback/custom values for concepts that do not have a platform specific equivalent.


But, fwiw, the spec already allows for authors to provide custom metadata (via the $extensions) and I've noticed that I've had to make extensive (pun intended) use of it to add custom logic around specific platform outputs.

Do you have concrete examples? Are these for supported token types in this specification?

Maybe best to open specific issues here for each. Afaik this specification is not intended to depend on $extensions for basic things.

chris-dura commented 1 year ago

Maybe best to open specific issues here for each. Afaik this specification is not intended to depend on $extensions for basic things.

Fair, but I'm not sure platform-specific things qualify as "basic"... at least not across the board. It kind of smells to me that an arbitrary update to iOS/Swift that Apple decides to make would/could require an update to the Tokens spec to support some new type or syntax in iOS 17? But, as you somewhat said, the "concept" of a keyword (or even more agnostic... the concept of an "unquoted string" type) may be universal enough to warrant placement in the spec?

Do you have concrete examples? Are these for supported token types in this specification?

Tbf, I'm mostly using $extensions to patch holes because the transformer I use (StyleDictionary) hasn't implemented the spec, yet...

$extensions: {
    deprecated: {
        renamed: "new-token-name",
        message: "@deprecated: Replace with 'new-token-name' token",
    },
    swift: {
        available: {
            platform: "iOS 13",
            introduced: "4.0.0",
            deprecated: "4.1.0",
            obsoleted: "5.0.0",
            unavailable: true,
        },
        optional: true,
        type: "UInt",
    },
    tier: "option" | "intent" | "component" | "decision"
},

Most of the above values are used in some custom output formats I've created. For example, I have a custom Swift format that outputs some @available() syntax that provides helpful dev info in Xcode. The swift.type: "UInt" property basically forces it to be a UInt, instead of the default CGFloat in the final Swift output.

However, the tier property could probably be handled by Groups as detailed in the spec, it's just Style Dictionary hasn't implemented that yet. (And, to be honest, even if they do, I may not want to author the Tokens that way, so the possibility to still want some custom metadata to exist is high)

romainmenke commented 1 year ago

But, as you somewhat said, the "concept" of a keyword (or even more agnostic... the concept of an "unquoted string" type) may be universal enough to warrant placement in the spec?

Hehe, no, it has to be the other way around. An unquoted string type would be platform specific.

{
  "$type": "unquoted-string", // or "keyword", "raw", ...
  "$value": "system-ui"
}

This token can only work in CSS. It is platform specific.


{
  "$type": "fontFamilyKeyword",
  "$value": "systemUI"
}

This is different from the regular fontFamily token type. Translation tools can then provide platform specific transforms.

Instead of generating a string value ("systemUI") then can do :

This works because there are two bits of information :


There are multiple ways to have the same outcome :

{
  "$type": "fontFamily",
  "$keyword": true,
  "$value": "systemUI"
}

...


abusing/overloading the word "keyword" a bit, but it is closest to what is intended