Retrieving and working with the tokens
Get a SDTFClient
SDTFClient
The SDTFClient
is a class providing numerous methods to work with the design data stored using the Specify Design Token Format (SDTF).
It's the way of interacting with your tokens and generating content.
Get the SDTF JSON token tree
You would want to grab the SDTF JSON token tree of a repository anytime you want to: send the token tree over the network or debug an intermediate manipulation.
Now that we know how to retrieve our SDTF, let's see how we can manipulate and retrieve our tokens.
Retrieving the tokens
There's multiple way to retrieve a token. If you want to work with a specific one you'll probably want to only get one, if you're developing a generic solution, you'll probably prefer iterating over many tokens. Let's have a look on how to do this.
Retrieving a single token
To retrieve a single token, you can just pick it by its path:
Retrieving all the tokens
Tokens
Groups
Collections
Retrieving specific tokens
If you need specifically some tokens, for example, based on the name, type, path, etc... You can perform a query and map over the results:
Or, you can only iterate over the results of your query if you need to perform mutations for example:
Looking for details about the query? 👉 heads up to the SDTF Query Language concept.
Looking for more methods to retrieve tokens? 👉 heads up to the SDTFEngine reference.
The token architecture
The TokenState
instance will be your main access to your tokens. Through this, you can retrieve your token data or perform updates. But first, let’s put some context and try to figure out what this TokenState
is trying to solve.
If we take a look at the token representation in the SDTF, it’s basically a JSON value where a lot of keys can be an alias rather than a value. Let’s have a look at all the possible cases with an example. Note that the following examples are 4 different ways of achieving the same result from a value point of view.
A basic token
An alias at the value level
An alias at the mode level
An alias at the top level
As you can see, there’s a lot of way to express a token, and dealing with all the possible cases is quite difficult. That’s why one of the goal of the TokenState
is to help you to deal with everything.
How to differentiate the tokens?
Before even thinking about retrieving the value of a token, we need to know what type of token we’re working with.
It’s actually quite easy to know as there’s a type
property in the TokenState
:
But even if we know the type, a problem will remain: Typescript doesn’t. TokenState
is quite a generic class as the default type is something that looks like this: TokenState<'dimension' | 'font' | 'breakpoint' | ...>
.
So the first mission is to be able to narrow down the type to be able to work with it. To do so, there’s a pretty convenient function:
You can match as much type as you need, and if some are not matched, they’ll be caught by the callback in the 2nd argument.
If you're working with Typescript, you may face some type issues while using matchByType
. A common example would be this:
If you copy/paste this code inside your editor, Typescript will complain and gives you the following error: Type '(_: TokenState<"breakpoint", Value, Mode>) => string' is not assignable to type '(token: TokenState<"breakpoint", Value, Mode>) => number | undefined'
.
Basically Typescript is unhappy that we return a number
and a string
. To fix the issue, there's 2 solutions:
Return the same type:
Excplicitely set the return type:
Depending on the use case, you'll prefer the first over the second solution, or the second over the first.
How to get the token value ?
Now that we know how to get the TokenState
that we want, we want to extract the value from it. As mentioned before, a token can have a lot of places that can contains an alias, so when retrieving the value, there’s 2 ways of doing it.
Ignoring aliasing
If you only want to get the raw value of a token and ignore the aliases, you can use the following function:
Also, as combining matchByType
and getJSONValue
can become a bit heavy. For that case, you can use the following function:
Handling the aliases
Overview of the API
If you want to support the aliases in your output, you’ll need to handle them when they are some at the top level, mode level, or value level. To do so, we provide the Stateful Value API that helps you to make sure you handle all the possible cases. Let’s see an example and then break down everything.
Although this API is a bit verbose, it’ll make sure that you cannot miss a case. So, what’s happening?
When you retrieve the value of a token, we put it inside a StatefulResult
, and it’ll expose the following type union at each level:
Top level:
ResolvableTopLevelAlias | UnresolvableTopLevelAlias | TopLevelValue
Mode level:
ResolvableModeLevelAlias | UnresolvableModeLevelAlias | RawValueSignature
Value level:
ResolvableValueLevelAlias | UnresolvableValueLevelAlias | string | number | ...
Each mapping function is dedicated to handling one case.
First, you’ll need to handle the top level:
Note the unwrap
at the end which is the way to extract the value.
Then you’ll want to handle the mode level:
All you need to do for the mode level is to return a value, and it'll be mapped to the right mode.
And finally the value level (we take a dimension token as an example):
If you call mapPrimitiveValue
, it needs to be the first one to be called in order to avoid remapping results produced by mapUnresolvableValueLevelAlias
or mapResolvableValueLevelAlias
.
As you can see each step is always the same: handling unresolvable alias, resolvable alias and the value.
Resolving an alias
Sometimes, you need to use the precision of the stateful result API, but don't want to deal with the aliases at some levels. To avoid doing so, you can resolve an alias by calling resolveDeepValue
, and you'll be left with handling the unresolvable alias and value case. Here is an example:
Handling only the value case
Although we could get rid of the resolvable alias case, handling the unresolvable case might still be too verbose. So let me introduce you to a nice pattern to only deal with values:
We will check if the token is fully resolvable
We'll use
resolveDeepValue
to remove the need of handling the resolvable aliasesWe'll use
unwrapValue
to remove the need of handling the unresolvable case at the type level
Thanks to this pattern, we went from a really verbose API to something lighter.
Last updated