@interactivethings/scripts 0.0.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/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # ixt-scripts
2
+
3
+ Scripts shared across projects
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ # Edit code
9
+ yarn publish
10
+ ```
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ yarn add @interactivethings/scripts
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ In the following the steps, bun is used but you can also use ts-node as
21
+ a Typescript runtime.
22
+
23
+ ### Tokens studio JSON to MUI JSON
24
+
25
+ ```bash
26
+ # Add this command to package.json scripts
27
+ bun ./node_modules/@interactivethings/scripts/mui-tokens-studio.ts src/styles/tokens.studio.json src/styles/tokens.mui.json
28
+ ```
@@ -0,0 +1,226 @@
1
+ /**
2
+ * This script transforms tokens exported with tokens-studio in Figma into
3
+ * a format that is easier to work with when using MUI.
4
+ * You can run it via `pnpm run design:tokens`
5
+ */
6
+
7
+ import fs from "fs";
8
+
9
+ import { ArgumentParser } from "argparse";
10
+ import { mapValues, pick } from "remeda";
11
+ import { $IntentionalAny } from "src/utils/types";
12
+
13
+ const simplifyValues = (
14
+ x: { value: unknown } | string | null
15
+ ): $IntentionalAny => {
16
+ if (typeof x === "string") {
17
+ return x;
18
+ } else if (typeof x === "object" && !!x) {
19
+ if ("value" in x) {
20
+ return x.value;
21
+ } else {
22
+ return mapValues(x, simplifyValues);
23
+ }
24
+ }
25
+ };
26
+
27
+ const kebabCase = (x: string) => {
28
+ return x
29
+ .split(" ")
30
+ .map((t, i) => {
31
+ if (i === 0) {
32
+ return t.toLowerCase();
33
+ } else {
34
+ return `${t[0].toUpperCase()}${t.substring(1).toLowerCase()}`;
35
+ }
36
+ })
37
+ .join("");
38
+ };
39
+ const renameColorKeys = (k: string) => {
40
+ return kebabCase(
41
+ k
42
+ .replace(/-[a-zA-Z0-9]+/, "")
43
+ .replace(" - ", " ")
44
+ .replace(",", "")
45
+ .replace(/^\d+\s+/, "")
46
+ );
47
+ };
48
+
49
+ const renameColorKeysEntries = (obj: $IntentionalAny) => {
50
+ const visitor = (k: string, v: $IntentionalAny) => {
51
+ if (typeof v === "object") {
52
+ return [renameColorKeys(k), mapEntries(v, visitor)] as [
53
+ string,
54
+ $IntentionalAny
55
+ ];
56
+ } else {
57
+ return [renameColorKeys(k), v] as [string, $IntentionalAny];
58
+ }
59
+ };
60
+ return mapEntries(obj, visitor);
61
+ };
62
+
63
+ const mapEntries = (
64
+ x: $IntentionalAny,
65
+ mapper: (k: string, v: $IntentionalAny) => [string, $IntentionalAny]
66
+ ) => {
67
+ return Object.fromEntries(Object.entries(x).map(([k, v]) => mapper(k, v)));
68
+ };
69
+
70
+ const getPalette = (tokensData: $IntentionalAny) => {
71
+ const data = tokensData.global;
72
+ const colorKeys = ["Base", "Functional"];
73
+
74
+ let palette = pick(data, colorKeys);
75
+ palette = mapValues(palette, simplifyValues);
76
+ palette = renameColorKeysEntries(palette);
77
+ palette = {
78
+ ...palette.base,
79
+ ...pick(palette, ["functional"]),
80
+ };
81
+ return palette;
82
+ };
83
+
84
+ const getTypography = (tokensData: $IntentionalAny) => {
85
+ const sizes = ["Desktop", "Mobile"];
86
+ const res = {} as Record<string, $IntentionalAny>;
87
+ const index = {} as Record<string, $IntentionalAny>;
88
+ for (const k of [
89
+ "fontFamilies",
90
+ "lineHeights",
91
+ "fontWeights",
92
+ "fontSize",
93
+ "letterSpacing",
94
+ "textDecoration",
95
+ ]) {
96
+ for (const [vName, value] of Object.entries(tokensData.global[k])) {
97
+ index[`${k}.${vName}`] = value;
98
+ }
99
+ }
100
+
101
+ const maybeParseToNumber = (x: string | number) => {
102
+ const parsed = Number(x);
103
+ if (Number.isNaN(x)) {
104
+ return x;
105
+ } else {
106
+ return parsed;
107
+ }
108
+ };
109
+
110
+ const maybeParseToPx = (x: string | number) => {
111
+ if (typeof x === "number") {
112
+ return `${x}px`;
113
+ } else {
114
+ return x;
115
+ }
116
+ };
117
+
118
+ const cleanupFns = {
119
+ fontWeight: (x: string) => {
120
+ const lowered = x.toLowerCase();
121
+ if (lowered == "regular") {
122
+ return 400;
123
+ }
124
+ return lowered;
125
+ },
126
+ fontSize: maybeParseToNumber,
127
+ lineHeight: (x: string | number) => maybeParseToPx(maybeParseToNumber(x)),
128
+ paragraphSpacing: maybeParseToNumber,
129
+ letterSpacing: maybeParseToNumber,
130
+ };
131
+
132
+ const cleanup = (k: string, v: $IntentionalAny) => {
133
+ if (k in cleanupFns) {
134
+ return [k, cleanupFns[k as keyof typeof cleanupFns](v)] as [
135
+ string,
136
+ $IntentionalAny
137
+ ];
138
+ } else {
139
+ return [k, v] as [string, $IntentionalAny];
140
+ }
141
+ };
142
+
143
+ const resolve = (str: string) => {
144
+ if (str[0] === "{" && str[str.length - 1] === "}") {
145
+ const path = str.substring(1, str.length - 1);
146
+ return index[path]?.value;
147
+ } else {
148
+ return str;
149
+ }
150
+ };
151
+ for (const size of sizes) {
152
+ const typographies = tokensData.global[size];
153
+ for (const [typo, typoDataRaw] of Object.entries(typographies)) {
154
+ const typoData = typoDataRaw as { value: $IntentionalAny };
155
+ const typoKey = kebabCase(typo.toLowerCase());
156
+ res[typoKey] = res[typoKey] || {};
157
+ res[typoKey][size.toLowerCase()] = mapEntries(
158
+ mapValues(typoData.value, resolve),
159
+ cleanup
160
+ );
161
+ }
162
+ }
163
+ return res;
164
+ };
165
+
166
+ const getShadows = (tokenData: TokenData) => {
167
+ const transformShadow = (shadowData: ShadowValue) => {
168
+ const { color, x, y, blur, spread } = shadowData;
169
+ return `${x}px ${y}px ${blur}px ${spread}px ${color}`;
170
+ };
171
+
172
+ const shadows = mapEntries(tokenData.global.Elevation, (k, v) => [
173
+ `${Number(k.replace("dp", "").replace("pd", ""))}`,
174
+ Array.isArray(v.value)
175
+ ? v.value.map(transformShadow).join(", ")
176
+ : transformShadow(v.value),
177
+ ]);
178
+ return shadows;
179
+ };
180
+
181
+ type ShadowValue = {
182
+ color: string;
183
+ x: string;
184
+ y: string;
185
+ blur: string;
186
+ spread: string;
187
+ };
188
+ type ShadowRecord = Record<
189
+ string,
190
+ { value: ShadowValue } | { value: ShadowValue[] }
191
+ >;
192
+
193
+ type TokenData = {
194
+ global: {
195
+ Base: $IntentionalAny;
196
+ Function: $IntentionalAny;
197
+ Elevation: ShadowRecord;
198
+ };
199
+ };
200
+
201
+ const transform = (tokenData: TokenData) => {
202
+ const palette = getPalette(tokenData);
203
+ const typography = getTypography(tokenData);
204
+ const shadows = getShadows(tokenData);
205
+ return {
206
+ palette,
207
+ typography,
208
+ shadows,
209
+ };
210
+ };
211
+
212
+ const main = () => {
213
+ const parser = new ArgumentParser();
214
+ parser.add_argument("input");
215
+ parser.add_argument("output");
216
+ const args = parser.parse_args();
217
+ const content = JSON.parse(fs.readFileSync(args.input).toString());
218
+ const transformed = transform(content);
219
+
220
+ fs.writeFileSync(
221
+ args.output === "-" ? process.stdout.fd : args.output,
222
+ JSON.stringify(transformed, null, 2)
223
+ );
224
+ };
225
+
226
+ main();
package/package.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@interactivethings/scripts",
3
+ "version": "0.0.1",
4
+ "main": "index.js",
5
+ "repository": "git@github.com:interactivethings/ixt-scripts.git",
6
+ "author": "Interactive Things <we@interactivethings.com>",
7
+ "license": "MIT"
8
+ }