Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Types for CSS Keywords #177

Open
kevinmpowell opened this issue Oct 31, 2022 · 12 comments
Open

Types for CSS Keywords #177

kevinmpowell opened this issue Oct 31, 2022 · 12 comments

Comments

@kevinmpowell
Copy link
Contributor

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

@romainmenke
Copy link
Contributor

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

@romainmenke
Copy link
Contributor

#102 (comment)

@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
Copy link

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
Copy link
Contributor

romainmenke commented Dec 4, 2022

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
Copy link
Contributor

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
Copy link
Contributor

I think the underlying issue is this :

  • some values are "user defined"
  • some values are "platform defined"

User defined values :

  • "#ff00ff"
  • "Comic Sans MS"

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
Copy link

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
Copy link
Contributor

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
Copy link

chris-dura commented Dec 5, 2022

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
Copy link
Contributor

romainmenke commented Dec 5, 2022

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.

  • conceptual : A font family that matches the OS UI
  • css : system-ui
  • swift : .system()
  • design token : ?

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
Copy link

chris-dura commented Dec 5, 2022

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
Copy link
Contributor

romainmenke commented Dec 5, 2022

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 :

  • css: system-ui
  • swift: .system()
  • ...

This works because there are two bits of information :

  • declared that it is a keyword and not a user defined value
  • declared that it is a keyword for "fontFamily"

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants