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

Design Tokens (json) → XAML (for .NET MAUI) #977

Open
alexfi1in opened this issue May 18, 2023 · 7 comments
Open

Design Tokens (json) → XAML (for .NET MAUI) #977

alexfi1in opened this issue May 18, 2023 · 7 comments

Comments

@alexfi1in
Copy link

I develop applications on the .NET MAUI platform. Is there any way to convert from design tokens (json) to XAML format?

@dbanksdesign
Copy link
Member

@alexfi1in do you have an example output of XAML you are trying to achieve? It would help to see an example and we can work backwards from there.

@AntonKosenkoDX
Copy link

For the following json file

{
    "brown": {
    "50": {
      "value": "#EFEBE9",
      "type": "color"
    },
    "100": {
      "value": "#D7CCC8",
      "type": "color"
    },
    "200": {
      "value": "#BCAAA4",
      "type": "color"
    },
    "300": {
      "value": "#A1887F",
      "type": "color"
    },
    "400": {
      "value": "#8D6E63",
      "type": "color"
    },
    "500": {
      "value": "#795548",
      "type": "color"
    },
    "600": {
      "value": "#6D4C41",
      "type": "color"
    },
    "700": {
      "value": "#5D4037",
      "type": "color"
    },
    "800": {
      "value": "#4E342E",
      "type": "color"
    },
    "900": {
      "value": "#3E2723",
      "type": "color"
    }
  }
}

Converter should generate something like this:

<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    
    <Color x:Key="Brown50">#EFEBE9</Color>
    <Color x:Key="Brown100">#D7CCC8</Color>
    <Color x:Key="Brown200">#BCAAA4</Color>
    <Color x:Key="Brown300">#A1887F</Color>
    <Color x:Key="Brown400">#8D6E63</Color>
    <Color x:Key="Brown500">#795548</Color>
    <Color x:Key="Brown600">#6D4C41</Color>
    <Color x:Key="Brown700">#5D4037</Color>
    <Color x:Key="Brown800">#4E342E</Color>
    <Color x:Key="Brown900">#3E2723</Color>

    <SolidColorBrush x:Key="Brown50Brush" Color="{StaticResource Brown50}"/>
    <SolidColorBrush x:Key="Brown100Brush" Color="{StaticResource Brown100}"/>
    <SolidColorBrush x:Key="Brown200Brush" Color="{StaticResource Brown200}"/>
    <SolidColorBrush x:Key="Brown300Brush" Color="{StaticResource Brown300}"/>
    <SolidColorBrush x:Key="Brown400Brush" Color="{StaticResource Brown400}"/>
    <SolidColorBrush x:Key="Brown500Brush" Color="{StaticResource Brown500}"/>
    <SolidColorBrush x:Key="Brown600Brush" Color="{StaticResource Brown600}"/>
    <SolidColorBrush x:Key="Brown700Brush" Color="{StaticResource Brown700}"/>
    <SolidColorBrush x:Key="Brown800Brush" Color="{StaticResource Brown800}"/>
    <SolidColorBrush x:Key="Brown900Brush" Color="{StaticResource Brown900}"/>
    
</ResourceDictionary>

But this is very simple example, xaml styles also support a lot complex and nested properties. The documentation for xaml styles syntax may be found here:
https://learn.microsoft.com/en-us/windows/apps/design/style/xaml-styles

@chazzmoney
Copy link
Collaborator

chazzmoney commented May 30, 2023

You could create a format and then use registerFormat. Something like this could get you started:

const XamlFormat = {
  name: 'xaml',
  formatter: function ({ dictionary, options }) {
    // Generate XAML content based on the design tokens in the dictionary
    let xamlContent = `<?xml version="1.0" encoding="UTF-8" ?>\n<?xaml-comp compile="true" ?>\n<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                               xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">`;

    // Generate the <Color> elements
    let colorElements = '';
    for (const tokenName in dictionary) {
      const tokenValue = dictionary[tokenName];

      // Create <Color> element based on the token name and value
      const colorElement = `<Color x:Key="${tokenName}">${tokenValue}</Color>`;
      colorElements += colorElement;
    }

    // Add the <Color> elements block to the XAML content
    xamlContent += colorElements;

    // Generate the <SolidColorBrush> elements
    let brushElements = '';
    for (const tokenName in dictionary) {
      // Create <SolidColorBrush> element based on the token name
      const brushElement = `<SolidColorBrush x:Key="${tokenName}Brush" Color="{StaticResource ${tokenName}}" />`;
      brushElements += brushElement;
    }

    // Add the <SolidColorBrush> elements block to the XAML content
    xamlContent += brushElements;

    // Close the ResourceDictionary tag
    xamlContent += `</ResourceDictionary>`;

    return xamlContent;
  }
};

module.exports = XamlFormat;

The "hard" part is really going to be how to decide what XAML element to create for each token, and then making sure to filter only to the tokens you want.

To use this format, you need to register it using the registerFormat function:

const StyleDictionary = require('style-dictionary');
const XamlFormat = require('./path/to/xaml-format');

StyleDictionary.registerFormat(XamlFormat);

After registering the format, you can use it when running StyleDictionary's build command, specifying xaml as the format:

style-dictionary build --format xaml

You can find more info on building a custom format here: https://amzn.github.io/style-dictionary/#/formats?id=custom-formats

@hansmbakker
Copy link

hansmbakker commented Jan 10, 2024

For my project I created a converter for MAUI.

Our approach is this: based on the JSON exported from Figma, it can generate C# classes with static properties.

The class members can be used in XAML like

{x:Static styles:ColorTokens.TextOnDefaultBackgroundStrong}

Another option would be to create a markupextension to reference them.

Properties can also reference each other (so that e.g. only color primitives have values, color aliases and color tokens are references to other static members - to prevent redundant calls to Color.FromRgba(...)):

public class ColorPrimitives
{
    public static Color Red = Color.FromRgba("#ff0000ff");
}

public class ColorTokens
{
    public static Color Warning = ColorPrimitives.Red;
}

The tokens are grouped in classes based on their path. This allows to have separate classes for colors, spacings (sizes for paddings and margins), effects (shadows), typography (font size, font family, weight, line height, etc).

What I haven't figured out yet is how best to create combinations of typography values: in Figma one typography token (e.g. BodyRegularXl, Heading01, BodyBoldSm) contains multiple values (font size, font family, weight, line height, etc). <style> only allows one TargetType unfortunately 🤦🏼‍♂️.

So it might require to create multiple styles for the same typography token- for Label, Button, Entry, etc.

@jorenbroekema @chazzmoney if I would want to contribute a converter for MAUI, would somebody be available to give the required feedback and assistance to get a PR merged?

Would others from this topic like to contribute?

Note: I still have to discuss internally how much we can share, but already wanted to check here as well, and I could already share our approach above.

Feedback to my approach is also appreciated! 🙏

@jorenbroekema
Copy link
Collaborator

jorenbroekema commented Jan 10, 2024

Hi, yes you can tag me and I'll review it. Thanks for taking the steps towards contributing this!

If it's okay with you, I would prefer if you could use the v4 branch of style-dictionary, the latest should also allow you to use the reference utils that are now exposed to make it easier to put references/aliases in the output as you've described.

import { usesReferences, getReferences } from 'style-dictionary/utils';

// pseudo code to get the token value or platform-specific alias/reference if outputReferences is enabled
function compileTokenValue(token) => {
  let value = token.value;
  const original = token.original.value;
  if (options.outputReferences && usesReferences(original)) {
    const refs = getReferences(dictionary, original);
    refs.forEach(ref => {
      // '{foo.bar.qux}' replaced by FooBarQux but since you're grouping primitives, your implementation will differ slightly
      value = token.value.replace(new RegExp(`{${ref.path.join('.')}(.value)?}`), 'g'), ref.name)
    });
  }
  return value;
}

My advice would be to start with some tests, then write the implementation and create a draft PR and I'll help you finalize it

@hansmbakker
Copy link

Great! I'm still in the process of getting approval, sorry.

Regarding the tests and structuring the code I probably could use some help later.

@hansmbakker
Copy link

hansmbakker commented Apr 16, 2024

@jorenbroekema I started work on this in #1162 - I started on the basics with exporting colors. I'm exporting to C# rather than XAML, so that they are also usable in C#, and so that you can generate compilable code. If you want to have a separate issue for this, please let me know.

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

No branches or pull requests

6 participants