@esri/calcite-design-tokens 1.0.0 → 1.1.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +85 -0
- package/.turbo/turbo-build.log +64 -0
- package/.turbo/turbo-test.log +14 -0
- package/CHANGELOG.md +17 -4
- package/CONTRIBUTING.md +41 -0
- package/LICENSE.md +13 -0
- package/README.md +9 -11
- package/dist/css/brand-light.css +1 -1
- package/dist/css/calcite-dark.css +1 -1
- package/dist/css/calcite-headless.css +2 -1
- package/dist/css/calcite-light.css +1 -1
- package/dist/scss/brand-light.scss +1 -1
- package/dist/scss/calcite-dark.scss +1 -1
- package/dist/scss/calcite-headless.scss +3 -1
- package/dist/scss/calcite-light.scss +1 -1
- package/jest.config.json +16 -0
- package/package.json +11 -72
- package/src/$metadata.json +76 -0
- package/src/$themes.json +2360 -0
- package/src/brand/dark.json +1 -0
- package/src/brand/global.json +1 -0
- package/src/brand/light.json +20 -0
- package/src/calcite/dark.json +2488 -0
- package/src/calcite/light.json +2508 -0
- package/src/component/accordion-item.json +172 -0
- package/src/component/accordion.json +192 -0
- package/src/component/action-bar-grid.json +66 -0
- package/src/component/action-bar.json +66 -0
- package/src/component/action-pad-grid.json +80 -0
- package/src/component/action-pad.json +80 -0
- package/src/component/action.json +156 -0
- package/src/component/alert.json +258 -0
- package/src/component/avatar.json +140 -0
- package/src/component/block-section.json +124 -0
- package/src/component/block.json +198 -0
- package/src/component/button.json +650 -0
- package/src/component/card.json +116 -0
- package/src/component/checkbox.json +110 -0
- package/src/component/chip.json +382 -0
- package/src/component/color-picker.json +148 -0
- package/src/component/combobox.json +152 -0
- package/src/component/date-picker.json +354 -0
- package/src/component/dropdown-item.json +384 -0
- package/src/component/dropdown.json +58 -0
- package/src/component/fab.json +490 -0
- package/src/component/filter.json +174 -0
- package/src/component/input-date-picker.json +224 -0
- package/src/component/input-datetime-local.json +230 -0
- package/src/component/input-email.json +244 -0
- package/src/component/input-file.json +244 -0
- package/src/component/input-message.json +72 -0
- package/src/component/input-month.json +244 -0
- package/src/component/input-number.json +244 -0
- package/src/component/input-password.json +244 -0
- package/src/component/input-search.json +244 -0
- package/src/component/input-telephone.json +244 -0
- package/src/component/input-text.json +244 -0
- package/src/component/input-time.json +1 -0
- package/src/component/input-week.json +244 -0
- package/src/component/label.json +26 -0
- package/src/component/link.json +44 -0
- package/src/component/loader.json +130 -0
- package/src/component/modal.json +278 -0
- package/src/component/notice.json +280 -0
- package/src/component/pagination.json +152 -0
- package/src/component/panel-header.json +88 -0
- package/src/component/popover.json +170 -0
- package/src/component/radio.json +124 -0
- package/src/component/rating.json +243 -0
- package/src/component/scrim.json +18 -0
- package/src/component/segmented-control.json +154 -0
- package/src/component/slider-histogram-range.json +284 -0
- package/src/component/slider-histogram.json +280 -0
- package/src/component/slider-range.json +226 -0
- package/src/component/slider.json +226 -0
- package/src/component/split-button.json +830 -0
- package/src/component/stepper-item.json +372 -0
- package/src/component/stepper.json +152 -0
- package/src/component/switch.json +178 -0
- package/src/component/tab-title.json +228 -0
- package/src/component/tabs.json +242 -0
- package/src/component/textarea.json +200 -0
- package/src/component/time-picker.json +138 -0
- package/src/component/tip-manager.json +118 -0
- package/src/component/tip.json +114 -0
- package/src/component/tooltip.json +66 -0
- package/src/component/tree-item.json +176 -0
- package/src/core.json +1709 -0
- package/src/semantic.json +1709 -0
- package/support/run.ts +16 -0
- package/support/token-transformer/format/scss.ts +81 -0
- package/support/token-transformer/getThemes.ts +41 -0
- package/support/token-transformer/parse/expandComposites.test.ts +144 -0
- package/support/token-transformer/parse/expandComposites.ts +72 -0
- package/support/token-transformer/sd-run.ts +147 -0
- package/support/token-transformer/transform/nameCamelCase.test.ts +36 -0
- package/support/token-transformer/transform/nameCamelCase.ts +15 -0
- package/support/token-transformer/transform/nameKebabCase.test.ts +36 -0
- package/support/token-transformer/transform/nameKebabCase.ts +26 -0
- package/support/token-transformer/utils/compositeTokens.test.ts +133 -0
- package/support/token-transformer/utils/compositeTokens.ts +103 -0
- package/support/token-transformer/utils/convertTokenToStyleDictionaryFormat.ts +20 -0
- package/support/token-transformer/utils/matchList.ts +11 -0
- package/support/token-transformer/utils/parseName.ts +18 -0
- package/support/token-transformer/utils/parseTokenPath.ts +19 -0
- package/support/token-transformer/utils/regex.ts +9 -0
- package/support/token-transformer/utils/sortAllTokens.ts +12 -0
- package/support/token-transformer/utils/transformOptions.ts +31 -0
- package/tsconfig-base.json +17 -0
- package/tsconfig-eslint.json +4 -0
- package/tsconfig.json +4 -0
package/support/run.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { resolve, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { getThemes } from "./token-transformer/getThemes.js";
|
|
5
|
+
import { run } from "./token-transformer/sd-run.js";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get all themes defined int the tokens/$themes.json and generate a Style Dictionary output for each theme
|
|
12
|
+
*/
|
|
13
|
+
const rawData = readFileSync(resolve(__dirname, "../src/$themes.json"), { encoding: "utf-8" });
|
|
14
|
+
const data = JSON.parse(rawData);
|
|
15
|
+
|
|
16
|
+
getThemes(data).then((themes) => Promise.all(themes.map((theme) => run("src", "dist", theme))));
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { pascalCase, sentenceCase } from "change-case";
|
|
2
|
+
import StyleDictionary, { Dictionary, File, Platform, Options } from "style-dictionary";
|
|
3
|
+
import { sortAllTokens } from "../utils/sortAllTokens.js";
|
|
4
|
+
|
|
5
|
+
const regexThemeGroup = /calcite|brand/gi;
|
|
6
|
+
const regexFileNameWithoutExtension = /\w+(?=\.\w+$)/gi;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Exports SCSS style formats
|
|
10
|
+
*
|
|
11
|
+
* @param {object} fileInfo the file object
|
|
12
|
+
* @param {Dictionary} fileInfo.dictionary the Style Dictionary object
|
|
13
|
+
* @param {File} fileInfo.file information about the file to be generated
|
|
14
|
+
* @param {Platform} [fileInfo.platform] the platform to generate the asset for
|
|
15
|
+
* @param {Options} fileInfo.options the Style Dictionary format options passed from the config
|
|
16
|
+
* @returns {string} a string that is passed to fs.writeFileSync
|
|
17
|
+
*/
|
|
18
|
+
export function formatSCSS(fileInfo: {
|
|
19
|
+
dictionary: Dictionary;
|
|
20
|
+
file: File;
|
|
21
|
+
platform?: Platform;
|
|
22
|
+
options: Options & { themeable?: boolean };
|
|
23
|
+
}): string {
|
|
24
|
+
const { dictionary, file, options } = fileInfo;
|
|
25
|
+
const { outputReferences } = options;
|
|
26
|
+
const themeName = pascalCase(
|
|
27
|
+
sentenceCase(file.destination.match(regexFileNameWithoutExtension)[0])
|
|
28
|
+
.split(" ")
|
|
29
|
+
.filter((n) => !regexThemeGroup.test(n))
|
|
30
|
+
.join(" ")
|
|
31
|
+
).toLowerCase();
|
|
32
|
+
const sassProps = StyleDictionary.formatHelpers.createPropertyFormatter({
|
|
33
|
+
outputReferences,
|
|
34
|
+
dictionary,
|
|
35
|
+
format: "sass",
|
|
36
|
+
});
|
|
37
|
+
const cssProps = StyleDictionary.formatHelpers.createPropertyFormatter({
|
|
38
|
+
outputReferences,
|
|
39
|
+
dictionary,
|
|
40
|
+
format: "css",
|
|
41
|
+
});
|
|
42
|
+
const sortedTokens = sortAllTokens(dictionary, outputReferences);
|
|
43
|
+
const coreTokens = [...sortedTokens].reduce(
|
|
44
|
+
(acc, token) => {
|
|
45
|
+
token.value = token.value.includes(" ") ? `"${token.value}"` : token.value === "Demi" ? 600 : token.value;
|
|
46
|
+
acc[1].push(cssProps(token));
|
|
47
|
+
|
|
48
|
+
if (token.filePath.includes("core")) {
|
|
49
|
+
const sassToken = { ...token };
|
|
50
|
+
const path = sassToken.path.filter((p) => !/(core|default|font$)/.test(p));
|
|
51
|
+
sassToken.name = sassToken.type === "color" ? path.slice(-1).join("-") : path.join("-");
|
|
52
|
+
sassToken.original.value = sassToken.original.value[0] === "{" ? sassToken.value : sassToken.original.value;
|
|
53
|
+
acc[0].push(sassProps(sassToken));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (/dark|light/.test(token.filePath) && !token.path.includes("component")) {
|
|
57
|
+
const sassToken = { ...token };
|
|
58
|
+
const path = sassToken.path.reduce((acc, p) => {
|
|
59
|
+
if (p === "default") {
|
|
60
|
+
return acc;
|
|
61
|
+
}
|
|
62
|
+
acc.push(p === "color" ? "ui" : p);
|
|
63
|
+
return acc;
|
|
64
|
+
}, []);
|
|
65
|
+
path.push(token.filePath.includes("dark") ? "dark" : "light");
|
|
66
|
+
sassToken.name = path.join("-");
|
|
67
|
+
acc[0].push(sassProps(sassToken));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return acc;
|
|
71
|
+
},
|
|
72
|
+
[[], []]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return `${StyleDictionary.formatHelpers.fileHeader({ file })}
|
|
76
|
+
${coreTokens[0].join("\n")}
|
|
77
|
+
|
|
78
|
+
@mixin calcite-theme-${themeName}() {
|
|
79
|
+
${coreTokens[1].join("\n")}
|
|
80
|
+
}`;
|
|
81
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Options } from "style-dictionary";
|
|
2
|
+
|
|
3
|
+
export interface ThemeFileInterface {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
selectedTokenSets: Record<string, "enabled" | "disabled" | "source">;
|
|
7
|
+
$figmaStyleReferences?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type Theme = {
|
|
11
|
+
name: string;
|
|
12
|
+
enabled: string[];
|
|
13
|
+
disabled: string[];
|
|
14
|
+
source: string[];
|
|
15
|
+
id?: string;
|
|
16
|
+
options?: Options;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param {ThemeFileInterface} themes an array of Figma Token Studio theme definition objects
|
|
22
|
+
* @returns {Array} an array of Style Dictionary theme definition objects
|
|
23
|
+
*/
|
|
24
|
+
export async function getThemes(themes: ThemeFileInterface[]): Promise<Theme[]> {
|
|
25
|
+
return themes.map((themeConfig) => {
|
|
26
|
+
const themeTypes = { enabled: [], disabled: [], source: [] };
|
|
27
|
+
const { name, id, selectedTokenSets } = themeConfig;
|
|
28
|
+
const { enabled, disabled, source } = Object.entries(selectedTokenSets).reduce((acc, [key, value]) => {
|
|
29
|
+
acc[value].push(key);
|
|
30
|
+
return acc;
|
|
31
|
+
}, themeTypes);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
name,
|
|
35
|
+
id,
|
|
36
|
+
enabled,
|
|
37
|
+
disabled,
|
|
38
|
+
source,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const mockCorrectTypeCompoundToken = {
|
|
2
|
+
core: {
|
|
3
|
+
1: {
|
|
4
|
+
type: "sizing",
|
|
5
|
+
value: "10",
|
|
6
|
+
},
|
|
7
|
+
},
|
|
8
|
+
compound: {
|
|
9
|
+
value: {
|
|
10
|
+
fontFamily: "$core.font.font-family.primary",
|
|
11
|
+
fontWeight: "$core.font.font-weight.light",
|
|
12
|
+
lineHeight: "$core.font.line-height.fixed.0",
|
|
13
|
+
fontSize: "$core.font.font-size.0",
|
|
14
|
+
letterSpacing: "$core.font.letter-spacing.normal",
|
|
15
|
+
paragraphSpacing: "$core.font.paragraph-spacing.normal",
|
|
16
|
+
textDecoration: "$core.font.text-decoration.none",
|
|
17
|
+
textCase: "$core.font.text-case.none",
|
|
18
|
+
},
|
|
19
|
+
type: "typography",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const mockTransformedCompoundTokens = {
|
|
23
|
+
core: {
|
|
24
|
+
1: {
|
|
25
|
+
type: "sizing",
|
|
26
|
+
value: "10",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
compound: {
|
|
30
|
+
"font-family": {
|
|
31
|
+
value: "$core.font.font-family.primary",
|
|
32
|
+
type: "font-family",
|
|
33
|
+
},
|
|
34
|
+
"font-weight": {
|
|
35
|
+
value: "$core.font.font-weight.light",
|
|
36
|
+
type: "font-weights",
|
|
37
|
+
},
|
|
38
|
+
"line-height": {
|
|
39
|
+
value: "$core.font.line-height.fixed.0",
|
|
40
|
+
type: "line-heights",
|
|
41
|
+
},
|
|
42
|
+
"font-size": {
|
|
43
|
+
value: "$core.font.font-size.0",
|
|
44
|
+
type: "font-size",
|
|
45
|
+
},
|
|
46
|
+
"letter-spacing": {
|
|
47
|
+
value: "$core.font.letter-spacing.normal",
|
|
48
|
+
type: "letter-spacing",
|
|
49
|
+
},
|
|
50
|
+
"paragraph-spacing": {
|
|
51
|
+
value: "$core.font.paragraph-spacing.normal",
|
|
52
|
+
type: "paragraph-spacing",
|
|
53
|
+
},
|
|
54
|
+
"text-decoration": {
|
|
55
|
+
value: "$core.font.text-decoration.none",
|
|
56
|
+
type: "font-style",
|
|
57
|
+
},
|
|
58
|
+
"text-case": {
|
|
59
|
+
value: "$core.font.text-case.none",
|
|
60
|
+
type: "text-case",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleTokenStudioVariables = jest.fn((token) => (token.includes("$") ? `{${token.replace(/\$/g, "")}}` : token));
|
|
66
|
+
const convertTokenToStyleDictionaryFormat = jest.fn(() => handleTokenStudioVariables);
|
|
67
|
+
const shouldExpand = jest.fn().mockReturnValue(true);
|
|
68
|
+
const expandToken = jest.fn().mockReturnValue(mockTransformedCompoundTokens);
|
|
69
|
+
|
|
70
|
+
jest.mock("../utils/compositeTokens.js", () => {
|
|
71
|
+
const originalModule = jest.requireActual("../utils/compositeTokens.js");
|
|
72
|
+
return {
|
|
73
|
+
__esModule: false,
|
|
74
|
+
...originalModule,
|
|
75
|
+
shouldExpand,
|
|
76
|
+
expandToken,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
jest.mock("../utils/convertTokenToStyleDictionaryFormat.js", () => {
|
|
81
|
+
const originalModule = jest.requireActual("../utils/convertTokenToStyleDictionaryFormat.js");
|
|
82
|
+
return {
|
|
83
|
+
__esModule: false,
|
|
84
|
+
...originalModule,
|
|
85
|
+
convertTokenToStyleDictionaryFormat,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
import * as expandComposites from "./expandComposites";
|
|
90
|
+
|
|
91
|
+
describe("expand token dictionary", () => {
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
jest.clearAllMocks();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should not add placeholder elements", () => {
|
|
97
|
+
const placeolderToken = {
|
|
98
|
+
"[placeholder-component]": {
|
|
99
|
+
type: "other",
|
|
100
|
+
value: "#333",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const placeholderValue = {
|
|
104
|
+
compoonent: {
|
|
105
|
+
type: "other",
|
|
106
|
+
value: "[placholder-value]",
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// @ts-expect-error - it's fine.
|
|
111
|
+
const testExpandPlaceholderKey = expandComposites.expandComposites(placeolderToken, "./fakePath");
|
|
112
|
+
// @ts-expect-error - it's fine.
|
|
113
|
+
const testExpandPlaceholderValue = expandComposites.expandComposites(placeholderValue, "./fakePath");
|
|
114
|
+
|
|
115
|
+
expect(testExpandPlaceholderKey).toMatchObject({});
|
|
116
|
+
expect(testExpandPlaceholderValue).toMatchObject({});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should loop through a dictionary and run "shouldExpand" and "expandToken" on each composite token', () => {
|
|
120
|
+
// @ts-expect-error - it's fine.
|
|
121
|
+
const testExpandComposite = expandComposites.expandComposites(mockCorrectTypeCompoundToken, "./fakePath");
|
|
122
|
+
expect(handleTokenStudioVariables).toHaveBeenCalledTimes(1);
|
|
123
|
+
expect(shouldExpand).toHaveBeenCalledTimes(1);
|
|
124
|
+
expect(expandToken).toHaveBeenCalledTimes(1);
|
|
125
|
+
expect(testExpandComposite).toMatchObject(mockTransformedCompoundTokens);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should not run expand token on unrecognized types", () => {
|
|
129
|
+
const mockDictionary = {
|
|
130
|
+
core: {
|
|
131
|
+
type: "customType",
|
|
132
|
+
value: {
|
|
133
|
+
fontFamily: "Avanir",
|
|
134
|
+
fontSize: "12px",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
// @ts-expect-error - it's fine this is a test
|
|
139
|
+
const testExpandComposite = expandComposites.expandComposites(mockDictionary, "./fakePath");
|
|
140
|
+
expect(shouldExpand).not.toHaveBeenCalled();
|
|
141
|
+
expect(expandToken).not.toHaveBeenCalled();
|
|
142
|
+
expect(testExpandComposite).toMatchObject(mockDictionary);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { DeepKeyTokenMap } from "@tokens-studio/types";
|
|
2
|
+
import { DesignToken } from "style-dictionary/types/DesignToken.js";
|
|
3
|
+
import {
|
|
4
|
+
TransformOptions,
|
|
5
|
+
ExpandablesAsStrings,
|
|
6
|
+
Expandables,
|
|
7
|
+
expandablesAsStringsArr,
|
|
8
|
+
} from "../utils/transformOptions.js";
|
|
9
|
+
import { matchPlaceholderElement, tokenStudioCustomVariableIndicator } from "../utils/regex.js";
|
|
10
|
+
import { shouldExpand, expandToken } from "../utils/compositeTokens.js";
|
|
11
|
+
import { convertTokenToStyleDictionaryFormat } from "../utils/convertTokenToStyleDictionaryFormat.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Figma Token Studio creates an odd type of composite token where the "value" may contain an object of "key: value" pairs rather than a sting.
|
|
15
|
+
* Here we will lift these composite tokens up to match the Style Dictionary format.
|
|
16
|
+
*
|
|
17
|
+
* @param {DeepKeyTokenMap} dictionary the raw JSON object in the token files. We will assume this is a token object generated by Figma Token Studio and may require composite tokens to be expanded.
|
|
18
|
+
* @param {string} filePath the absolute file path to the JSON token file.
|
|
19
|
+
* @param {TransformOptions} transformOpts the options passed in from the Style Dictionary config and runner
|
|
20
|
+
* @returns {DeepKeyTokenMap} a token object where any Figma Token Studio composite tokens now match the Style Dictionary token format
|
|
21
|
+
*/
|
|
22
|
+
export function expandComposites(
|
|
23
|
+
dictionary: DeepKeyTokenMap,
|
|
24
|
+
filePath: string,
|
|
25
|
+
transformOpts: TransformOptions = {}
|
|
26
|
+
): DeepKeyTokenMap {
|
|
27
|
+
const opts = {
|
|
28
|
+
...transformOpts,
|
|
29
|
+
expand: {
|
|
30
|
+
composition: true,
|
|
31
|
+
typography: false,
|
|
32
|
+
border: false,
|
|
33
|
+
shadow: false,
|
|
34
|
+
...transformOpts.expand,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
const returnSlice: DeepKeyTokenMap = {};
|
|
38
|
+
const handleTokenStudioVariables = convertTokenToStyleDictionaryFormat(tokenStudioCustomVariableIndicator);
|
|
39
|
+
const newDictionary = Object.entries(dictionary).reduce((acc, [key, token]) => {
|
|
40
|
+
const { type } = token;
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
matchPlaceholderElement.test(`${key}`) ||
|
|
44
|
+
(typeof token.value === "string" && matchPlaceholderElement.test(`${token.value}`))
|
|
45
|
+
) {
|
|
46
|
+
return acc;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (token.value && type) {
|
|
50
|
+
const includesType = expandablesAsStringsArr.includes(`${type}`);
|
|
51
|
+
|
|
52
|
+
if (includesType) {
|
|
53
|
+
const expandType = (type as ExpandablesAsStrings) === "boxShadow" ? "shadow" : type;
|
|
54
|
+
const expand = shouldExpand<Expandables>(token as Expandables, opts.expand[`${expandType}`], filePath);
|
|
55
|
+
if (expand) {
|
|
56
|
+
const expandedToken = expandToken(token as DesignToken, expandType === "shadow", handleTokenStudioVariables);
|
|
57
|
+
return expandedToken;
|
|
58
|
+
}
|
|
59
|
+
} else if (typeof token.value === "string") {
|
|
60
|
+
token.value = handleTokenStudioVariables(token.value);
|
|
61
|
+
acc[key] = token;
|
|
62
|
+
} else {
|
|
63
|
+
acc[key] = token;
|
|
64
|
+
}
|
|
65
|
+
} else if (typeof token === "object") {
|
|
66
|
+
acc[key] = expandComposites(token as DeepKeyTokenMap, filePath, transformOpts);
|
|
67
|
+
}
|
|
68
|
+
return acc;
|
|
69
|
+
}, returnSlice);
|
|
70
|
+
|
|
71
|
+
return newDictionary || {};
|
|
72
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { registerTransforms } from "@tokens-studio/sd-transforms";
|
|
2
|
+
import StyleDictionary from "style-dictionary";
|
|
3
|
+
import { expandComposites } from "./parse/expandComposites.js";
|
|
4
|
+
import { formatSCSS } from "./format/scss.js";
|
|
5
|
+
import { matchExclusions } from "./utils/regex.js";
|
|
6
|
+
import { matchList } from "./utils/matchList.js";
|
|
7
|
+
import { nameCamelCase } from "./transform/nameCamelCase.js";
|
|
8
|
+
import { nameKebabCase } from "./transform/nameKebabCase.js";
|
|
9
|
+
import { parseName } from "./utils/parseName.js";
|
|
10
|
+
import { Theme } from "./getThemes.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Style Dictionary runner configuration overrides.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} tokenDir the directory containing design token files
|
|
16
|
+
* @param {string} buildPath the directory to write generated assets to
|
|
17
|
+
* @param {Theme} theme the theme configuration to use to generate the platform asset files
|
|
18
|
+
* @param {string} theme.name the name of the theme. This will be used as the basis for the generated asset file names.
|
|
19
|
+
* @param {string[]} theme.enabled an array of partial file names matching the token files which should be included in the output
|
|
20
|
+
* @param {string[]} theme.disabled an array of partial file names matching the token files which should explicitly not be included in the output
|
|
21
|
+
* @param {string[]} theme.source an array of partial file names matching the token files which should not always be included in the output but who's values should be used for variables references in the "enabled" files
|
|
22
|
+
*/
|
|
23
|
+
export const run = async (
|
|
24
|
+
tokenDir = "tokens",
|
|
25
|
+
buildPath = "dist",
|
|
26
|
+
theme: Pick<Theme, "enabled" | "disabled" | "name" | "source">
|
|
27
|
+
): Promise<void> => {
|
|
28
|
+
const fileName = parseName(theme.name);
|
|
29
|
+
const include = theme.source.map((tokenFile) => `${tokenDir}/${tokenFile}.json`);
|
|
30
|
+
const source = theme.enabled.map((tokenFile) => `${tokenDir}/${tokenFile}.json`);
|
|
31
|
+
const options = {
|
|
32
|
+
enabled: theme.enabled,
|
|
33
|
+
source: theme.source,
|
|
34
|
+
disabled: theme.disabled,
|
|
35
|
+
outputReferences: false,
|
|
36
|
+
sourceReferencesOnly: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Here we are registering the Transforms provided by Token Studio
|
|
40
|
+
// https://github.com/tokens-studio/sd-transforms
|
|
41
|
+
// @ts-expect-error - @token-studio does not keep their types up to date.
|
|
42
|
+
await registerTransforms(StyleDictionary, {
|
|
43
|
+
expand: false,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Register custom formatter https://amzn.github.io/style-dictionary/#/formats?id=custom-formats
|
|
47
|
+
StyleDictionary.registerFormat({
|
|
48
|
+
name: "calcite/scss",
|
|
49
|
+
formatter: formatSCSS,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Registering Style Dictionary transformers https://amzn.github.io/style-dictionary/#/transforms?id=defining-custom-transforms
|
|
53
|
+
StyleDictionary.registerTransform({
|
|
54
|
+
name: "name/calcite/camel",
|
|
55
|
+
type: "name",
|
|
56
|
+
transformer: nameCamelCase,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
StyleDictionary.registerTransform({
|
|
60
|
+
name: "name/calcite/kebab",
|
|
61
|
+
type: "name",
|
|
62
|
+
transformer: nameKebabCase,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
StyleDictionary.registerFilter({
|
|
66
|
+
name: "filterSource",
|
|
67
|
+
matcher: (token) => token.isSource,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// We are programmatically creating the Style Dictionary configuration here
|
|
71
|
+
// https://amzn.github.io/style-dictionary/#/config
|
|
72
|
+
const sd = StyleDictionary.extend({
|
|
73
|
+
source,
|
|
74
|
+
include,
|
|
75
|
+
platforms: {
|
|
76
|
+
css: {
|
|
77
|
+
prefix: "calcite",
|
|
78
|
+
transforms: [
|
|
79
|
+
"ts/descriptionToComment",
|
|
80
|
+
"ts/size/px",
|
|
81
|
+
"ts/opacity",
|
|
82
|
+
"ts/size/lineheight",
|
|
83
|
+
"ts/type/fontWeight",
|
|
84
|
+
"ts/resolveMath",
|
|
85
|
+
"ts/size/css/letterspacing",
|
|
86
|
+
"ts/color/css/hexrgba",
|
|
87
|
+
"ts/color/modifiers",
|
|
88
|
+
"name/calcite/kebab",
|
|
89
|
+
],
|
|
90
|
+
buildPath: `${buildPath}/css/`,
|
|
91
|
+
files: [
|
|
92
|
+
{
|
|
93
|
+
destination: `${fileName}.css`,
|
|
94
|
+
format: "css/variables",
|
|
95
|
+
filter: /headless/gi.test(fileName) ? null : "filterSource",
|
|
96
|
+
options: /headless/gi.test(fileName) ? { ...options, outputReferences: true } : options,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
scss: {
|
|
101
|
+
prefix: "calcite",
|
|
102
|
+
transforms: [
|
|
103
|
+
"ts/descriptionToComment",
|
|
104
|
+
"ts/size/px",
|
|
105
|
+
"ts/opacity",
|
|
106
|
+
"ts/size/lineheight",
|
|
107
|
+
"ts/type/fontWeight",
|
|
108
|
+
"ts/resolveMath",
|
|
109
|
+
"ts/size/css/letterspacing",
|
|
110
|
+
"ts/color/css/hexrgba",
|
|
111
|
+
"ts/color/modifiers",
|
|
112
|
+
"name/calcite/kebab",
|
|
113
|
+
],
|
|
114
|
+
buildPath: `${buildPath}/scss/`,
|
|
115
|
+
files: [
|
|
116
|
+
{
|
|
117
|
+
destination: `${fileName}.scss`,
|
|
118
|
+
format: "calcite/scss",
|
|
119
|
+
filter: /headless/gi.test(fileName) ? null : "filterSource",
|
|
120
|
+
options: /headless/gi.test(fileName) ? { ...options, outputReferences: true } : options,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
parsers: [
|
|
126
|
+
{
|
|
127
|
+
pattern: /\.json$/,
|
|
128
|
+
parse: (file) => {
|
|
129
|
+
if (matchList(file.filePath, [...include, ...theme.source, ...theme.enabled], matchExclusions)) {
|
|
130
|
+
const obj = JSON.parse(file.contents);
|
|
131
|
+
const expanded = expandComposites(obj, file.filePath);
|
|
132
|
+
return expanded;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {};
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
sd.cleanAllPlatforms();
|
|
143
|
+
sd.buildAllPlatforms();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(error);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { nameCamelCase } from "./nameCamelCase";
|
|
2
|
+
|
|
3
|
+
describe("transform names to camel case", () => {
|
|
4
|
+
it("should transform a token path to token name in a camel case format", () => {
|
|
5
|
+
const mockToken = {
|
|
6
|
+
name: "current-name",
|
|
7
|
+
path: ["test", "token", "name"],
|
|
8
|
+
value: "fake-value",
|
|
9
|
+
filePath: "./fakePath.json",
|
|
10
|
+
original: {
|
|
11
|
+
value: "fake-value",
|
|
12
|
+
type: "composite",
|
|
13
|
+
},
|
|
14
|
+
isSource: true,
|
|
15
|
+
};
|
|
16
|
+
expect(nameCamelCase(mockToken, {})).toBe("testTokenName");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should add prefix to token name", () => {
|
|
20
|
+
const mockToken = {
|
|
21
|
+
name: "current-name",
|
|
22
|
+
path: ["test", "token", "name"],
|
|
23
|
+
value: "fake-value",
|
|
24
|
+
filePath: "./fakePath.json",
|
|
25
|
+
original: {
|
|
26
|
+
value: "fake-value",
|
|
27
|
+
type: "composite",
|
|
28
|
+
},
|
|
29
|
+
isSource: true,
|
|
30
|
+
};
|
|
31
|
+
const mockOptions = {
|
|
32
|
+
prefix: "calcite",
|
|
33
|
+
};
|
|
34
|
+
expect(nameCamelCase(mockToken, mockOptions)).toBe("calciteTestTokenName");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { camelCase } from "change-case";
|
|
2
|
+
import { TransformedToken } from "style-dictionary/types/TransformedToken.js";
|
|
3
|
+
import { Options } from "style-dictionary/types/Options.js";
|
|
4
|
+
import { parseTokenPath } from "../utils/parseTokenPath.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert token name to camel case
|
|
8
|
+
*
|
|
9
|
+
* @param {TransformedToken} token Style Dictionary token object
|
|
10
|
+
* @param {Options} options Style Dictionary format options
|
|
11
|
+
* @returns {string} an updated name for the token which will be used for the final output
|
|
12
|
+
*/
|
|
13
|
+
export function nameCamelCase(token: TransformedToken, options: Options): string {
|
|
14
|
+
return camelCase([options.prefix].concat(parseTokenPath(token.path)).join(" "));
|
|
15
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { nameKebabCase } from "./nameKebabCase";
|
|
2
|
+
|
|
3
|
+
describe("transform names to kebab case", () => {
|
|
4
|
+
it("should transform a token path to token name in a kebab case format", () => {
|
|
5
|
+
const mockToken = {
|
|
6
|
+
name: "current-name",
|
|
7
|
+
path: ["test", "token", "name"],
|
|
8
|
+
value: "fake-value",
|
|
9
|
+
filePath: "./fakePath.json",
|
|
10
|
+
original: {
|
|
11
|
+
value: "fake-value",
|
|
12
|
+
type: "composite",
|
|
13
|
+
},
|
|
14
|
+
isSource: true,
|
|
15
|
+
};
|
|
16
|
+
expect(nameKebabCase(mockToken, {})).toBe("test-token-name");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should add prefix to token name", () => {
|
|
20
|
+
const mockToken = {
|
|
21
|
+
name: "current-name",
|
|
22
|
+
path: ["test", "token", "name"],
|
|
23
|
+
value: "fake-value",
|
|
24
|
+
filePath: "./fakePath.json",
|
|
25
|
+
original: {
|
|
26
|
+
value: "fake-value",
|
|
27
|
+
type: "composite",
|
|
28
|
+
},
|
|
29
|
+
isSource: true,
|
|
30
|
+
};
|
|
31
|
+
const mockOptions = {
|
|
32
|
+
prefix: "calcite",
|
|
33
|
+
};
|
|
34
|
+
expect(nameKebabCase(mockToken, mockOptions)).toBe("calcite-test-token-name");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { paramCase } from "change-case";
|
|
2
|
+
import { TransformedToken } from "style-dictionary/types/TransformedToken.js";
|
|
3
|
+
import { Options } from "style-dictionary/types/Options.js";
|
|
4
|
+
import { parseTokenPath } from "../utils/parseTokenPath.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* convert token name to kebab case
|
|
8
|
+
*
|
|
9
|
+
* @param {TransformedToken} token Style Dictionary token object
|
|
10
|
+
* @param {Options} options Style Dictionary format options
|
|
11
|
+
* @returns {string} an updated name for the token which will be used for the final output
|
|
12
|
+
*/
|
|
13
|
+
export function nameKebabCase(token: TransformedToken, options: Options): string {
|
|
14
|
+
const paths = token.path.reduce((acc, p, idx) => {
|
|
15
|
+
if (p === "core") {
|
|
16
|
+
acc.push("app");
|
|
17
|
+
} else if (typeof token.path[idx + 1] === "string" && !new RegExp(`${p}`).test(token.path[idx + 1])) {
|
|
18
|
+
acc.push(p);
|
|
19
|
+
} else if (idx === token.path.length - 1) {
|
|
20
|
+
acc.push(p);
|
|
21
|
+
}
|
|
22
|
+
return acc;
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
return paramCase([options.prefix].concat(parseTokenPath(paths)).join(" "));
|
|
26
|
+
}
|