Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@ In order for us to review and merge your code, please follow the link and sign t
[create pr]: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork
[GitHub hub]: https://hub.github.com
[ssh key]: https://help.github.com/articles/generating-ssh-keys
[CLA]: https://simpleclub.page.link/cla
[versioning]: https://stackoverflow.com/questions/66201337/how-do-dart-package-versions-work-how-should-i-version-my-flutter-plugins/66201338#66201338
[CLA]: https://cla-assistant.io/simpleclub/
[versioning]: https://stackoverflow.com/questions/66201337/how-do-dart-package-versions-work-how-should-i-version-my-flutter-plugins/66201338#66201338
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2023 simpleclub GmbH. All rights reserved.
Copyright 2023-2024 simpleclub GmbH. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Right now, we support generating/parsing the following:
- Text styles

- Exposing theming and extensions via a `BuildContext` extension
- Markdown documentation generation for all tokens with their resolved values

## Getting started

Expand All @@ -55,8 +56,12 @@ targets:
builders:
design_tokens_builder:design_tokens_builder:
enabled: true
design_tokens_builder:markdown_documentation_builder:
enabled: true
```

4. (Optional) Enable the markdown documentation builder to generate a `tokens.md` file with all tokens and their resolved values for easy reference.

## Usage

Before you can use the tokens, you have to start the build runner by
Expand All @@ -80,6 +85,30 @@ properties like `context.colorScheme` and `context.textTheme`.

## Additional information

### Markdown documentation

The package can optionally generate a Markdown documentation file (`lib/tokens.md`) that lists all
design tokens with their resolved values. This is particularly useful when you need to look up the
actual value of a token reference like `context.color.fg.primary.base` in your codebase.

To enable markdown documentation generation, add the markdown builder to your `build.yaml`:

```yaml
targets:
$default:
builders:
design_tokens_builder:design_tokens_builder:
enabled: true
design_tokens_builder:markdown_documentation_builder:
enabled: true
```

After running the build runner, you'll find a `tokens.md` file in your `lib` directory with content
organized by token sets. Each token entry includes:
- The full token path (e.g., `global.fontSize.base`)
- The token type (e.g., `fontSizes`)
- The resolved value (e.g., `10` - with aliases and mathematical expressions evaluated)

### Multi-theming and dark and light mode

The package can generate themes based on
Expand Down
7 changes: 7 additions & 0 deletions build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ builders:
"$lib$": ["tokens.dart"]
}
build_to: source
markdown_documentation_builder:
import: "package:design_tokens_builder/design_tokens_builder.dart"
builder_factories: ["markdownDocumentationFactory"]
build_extensions: {
"$lib$": ["tokens.md"]
}
build_to: source
2 changes: 2 additions & 0 deletions example/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ targets:
builders:
design_tokens_builder:design_tokens_builder:
enabled: true
design_tokens_builder:markdown_documentation_builder:
enabled: true
1 change: 1 addition & 0 deletions lib/design_tokens_builder.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
library design_tokens_builder;

export 'factory/design_tokens_factory.dart';
export 'factory/markdown_documentation_factory.dart';
113 changes: 113 additions & 0 deletions lib/factory/markdown_documentation_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import 'dart:async';
import 'dart:convert';

import 'package:build/build.dart';
import 'package:design_tokens_builder/builder_config/builder_config.dart';
import 'package:design_tokens_builder/utils/transformer_utils.dart';
import 'package:glob/glob.dart';
import 'package:yaml/yaml.dart';

/// Builder for generating markdown documentation from design tokens.
Builder markdownDocumentationFactory(BuilderOptions _) =>
MarkdownDocumentationFactory();

/// Builder for generating a Markdown file that documents all tokens with their
/// resolved values.
class MarkdownDocumentationFactory implements Builder {
@override
final Map<String, List<String>> buildExtensions = {
r'tokens.json': ['tokens.md'],
};

@override
Future<void> build(BuildStep buildStep) async {
final inputId = buildStep.inputId;
print('inputId: ${inputId.path}');
final outputId = inputId.changeExtension('.dart');
final configFile =
(await buildStep.findAssets(Glob('**/tokenbuilder.yaml')).toList())
.first;
print('Parse config… ---------------------');

final configString = await buildStep.readAsString(configFile);
final yaml = loadYaml(configString) as YamlMap;
final config = BuilderConfig.fromYaml(yaml);

print('Get tokens… -----------------------');

final string = await buildStep.readAsString(inputId);
final token = jsonDecode(string) as Map<String, dynamic>;

print('Prepare tokens… -------------------');

final processedToken = prepareTokens(token, config: config);
final processedDefaultSet = processedToken[config.sourceSetConfig.prefix];

print('Start building documentation… ------------');

await buildStep.writeAsString(
AssetId(buildStep.inputId.package, 'lib/tokens.md'),
_generateMarkdown(processedToken, config),
);
}

/// Generates the markdown documentation from processed tokens.
String _generateMarkdown(
Map<String, dynamic> tokens,
BuilderConfig config,
) {
final buffer = StringBuffer();
buffer.writeln('# Design Tokens Documentation');
buffer.writeln();
buffer.writeln(
'This document lists all design tokens with their resolved values.',
);
buffer.writeln();

// Process each token set
for (final setEntry in tokens.entries) {
final setName = setEntry.key;

// Skip metadata entries
if (setName.startsWith('\$')) continue;

buffer.writeln('## Token Set: $setName');
buffer.writeln();

final setData = setEntry.value as Map<String, dynamic>;
_processTokenGroup(buffer, setData, setName);
buffer.writeln();
}

return buffer.toString();
}

/// Recursively processes a token group and adds entries to the buffer.
void _processTokenGroup(
StringBuffer buffer,
Map<String, dynamic> group,
String prefix,
) {
for (final entry in group.entries) {
final key = entry.key;
final value = entry.value;

if (value is Map<String, dynamic>) {
// Check if this is a leaf token (has 'value' and 'type' keys)
if (value.containsKey('value') && value.containsKey('type')) {
final tokenPath = '$prefix.$key';
final tokenValue = value['value'];
final tokenType = value['type'];

buffer.writeln('- **$tokenPath**');
buffer.writeln(' - Type: `$tokenType`');
buffer.writeln(' - Value: `$tokenValue`');
buffer.writeln();
} else {
// This is a nested group, recurse
_processTokenGroup(buffer, value, '$prefix.$key');
}
}
}
}
}
182 changes: 182 additions & 0 deletions test/factory/markdown_documentation_factory_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import 'package:build/build.dart';
import 'package:build_test/build_test.dart';
import 'package:design_tokens_builder/factory/markdown_documentation_factory.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('MarkdownDocumentationFactory', () {
test('generates markdown documentation for simple tokens', () async {
final builder = markdownDocumentationFactory(BuilderOptions.empty);

await testBuilder(
builder,
{
'a|lib/tokenbuilder.yaml': '''
tokenFilePath: lib/tokens.json
defaultSetName: global
''',
'a|lib/tokens.json': '''
{
"global": {
"white": {
"value": "#ffffff",
"type": "color"
},
"black": {
"value": "#293133",
"type": "color"
},
"fontSize": {
"base": {
"value": "10",
"type": "fontSizes"
}
}
},
"\$metadata": {
"tokenSetOrder": ["global"]
}
}
''',
},
outputs: {
'a|lib/tokens.md': decodedMatches(
allOf([
contains('# Design Tokens Documentation'),
contains('## Token Set: global'),
contains('- **global.white**'),
contains('Type: `color`'),
contains('Value: `#ffffff`'),
contains('- **global.black**'),
contains('Value: `#293133`'),
contains('- **global.fontSize.base**'),
contains('Type: `fontSizes`'),
contains('Value: `10`'),
]),
),
},
);
});

test('generates markdown documentation with resolved aliases', () async {
final builder = markdownDocumentationFactory(BuilderOptions.empty);

await testBuilder(
builder,
{
'a|lib/tokenbuilder.yaml': '''
tokenFilePath: lib/tokens.json
defaultSetName: global
''',
'a|lib/tokens.json': '''
{
"global": {
"white": {
"value": "#ffffff",
"type": "color"
},
"fontSize": {
"base": {
"value": "10",
"type": "fontSizes"
},
"scale": {
"value": "3",
"type": "fontSizes"
},
"sm": {
"value": "{fontSize.base}+{fontSize.scale}",
"type": "fontSizes"
}
}
},
"light": {
"sys": {
"background": {
"value": "{white}",
"type": "color"
}
}
},
"\$metadata": {
"tokenSetOrder": ["global", "light"]
}
}
''',
},
outputs: {
'a|lib/tokens.md': decodedMatches(
allOf([
contains('# Design Tokens Documentation'),
contains('## Token Set: global'),
contains('- **global.white**'),
contains('Value: `#ffffff`'),
contains('- **global.fontSize.sm**'),
contains('Value: `13.0`'),
contains('## Token Set: light'),
contains('- **light.sys.background**'),
contains('Value: `#ffffff`'),
]),
),
},
);
});

test('generates markdown documentation for multiple token sets', () async {
final builder = markdownDocumentationFactory(BuilderOptions.empty);

await testBuilder(
builder,
{
'a|lib/tokenbuilder.yaml': '''
tokenFilePath: lib/tokens.json
defaultSetName: global
''',
'a|lib/tokens.json': '''
{
"global": {
"white": {
"value": "#ffffff",
"type": "color"
}
},
"light": {
"sys": {
"primary": {
"value": "#0000FF",
"type": "color"
}
}
},
"dark": {
"sys": {
"primary": {
"value": "#000088",
"type": "color"
}
}
},
"\$metadata": {
"tokenSetOrder": ["global", "light", "dark"]
}
}
''',
},
outputs: {
'a|lib/tokens.md': decodedMatches(
allOf([
contains('# Design Tokens Documentation'),
contains('## Token Set: global'),
contains('## Token Set: light'),
contains('## Token Set: dark'),
contains('- **light.sys.primary**'),
contains('Value: `#0000FF`'),
contains('- **dark.sys.primary**'),
contains('Value: `#000088`'),
]),
),
},
);
});
});
}
Loading