inkhouse 0.1.0-beta.0

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.
Files changed (62) hide show
  1. package/README.md +201 -0
  2. package/bin/inkhouse.mjs +171 -0
  3. package/code.js +11802 -0
  4. package/manifest.json +30 -0
  5. package/package.json +45 -0
  6. package/scanner/blob-placement-regression.ts +132 -0
  7. package/scanner/class-collector.ts +69 -0
  8. package/scanner/cli.ts +336 -0
  9. package/scanner/component-scanner.ts +2876 -0
  10. package/scanner/css-patch-regression.ts +112 -0
  11. package/scanner/css-token-reader-regression.ts +92 -0
  12. package/scanner/css-token-reader.ts +477 -0
  13. package/scanner/font-style-resolver-regression.ts +32 -0
  14. package/scanner/index.ts +9 -0
  15. package/scanner/radial-gradient-regression.ts +53 -0
  16. package/scanner/style-map.ts +145 -0
  17. package/scanner/tailwind-parser.ts +644 -0
  18. package/scanner/transform-math-regression.ts +42 -0
  19. package/scanner/types.ts +298 -0
  20. package/src/blob-placement.ts +111 -0
  21. package/src/change-detection.ts +204 -0
  22. package/src/class-utils.ts +105 -0
  23. package/src/clip-path-decorative.ts +194 -0
  24. package/src/color-resolver.ts +98 -0
  25. package/src/colors.ts +196 -0
  26. package/src/component-defs.ts +54 -0
  27. package/src/component-gen.ts +561 -0
  28. package/src/component-lookup.ts +82 -0
  29. package/src/config.ts +115 -0
  30. package/src/design-system.ts +59 -0
  31. package/src/dev-server.ts +173 -0
  32. package/src/figma-globals.d.ts +3 -0
  33. package/src/font-style-resolver.ts +171 -0
  34. package/src/github.ts +1465 -0
  35. package/src/icon-builder.ts +607 -0
  36. package/src/image-cache.ts +22 -0
  37. package/src/inline-text.ts +271 -0
  38. package/src/layout-parser.ts +667 -0
  39. package/src/layout-utils.ts +155 -0
  40. package/src/main.ts +687 -0
  41. package/src/node-ir.ts +595 -0
  42. package/src/pack-provider.ts +148 -0
  43. package/src/packs.ts +126 -0
  44. package/src/radial-gradient.ts +84 -0
  45. package/src/render-context.ts +138 -0
  46. package/src/responsive-analyzer.ts +139 -0
  47. package/src/state-analyzer.ts +143 -0
  48. package/src/story-builder.ts +1706 -0
  49. package/src/story-layout.ts +38 -0
  50. package/src/tailwind.ts +2379 -0
  51. package/src/text-builder.ts +116 -0
  52. package/src/text-line.ts +42 -0
  53. package/src/token-source.ts +43 -0
  54. package/src/tokens.ts +717 -0
  55. package/src/transform-math.ts +44 -0
  56. package/src/ui-builder.ts +1996 -0
  57. package/src/utility-resolver.ts +125 -0
  58. package/src/variables.ts +1042 -0
  59. package/src/width-solver.ts +466 -0
  60. package/templates/patch-tokens-route.ts +165 -0
  61. package/templates/scan-components-route.ts +57 -0
  62. package/ui.html +1222 -0
@@ -0,0 +1,116 @@
1
+ import { parseColor } from './colors';
2
+ import { TOKENS, getCoreFontFamily, getThemeFontFamily } from './tokens';
3
+ import { tailwindClassesToStyle, type TailwindStyle } from './tailwind';
4
+ import { type NodeIR, isElementLikeNode } from './node-ir';
5
+ import { getFontStyleCandidatesForFamily } from './font-style-resolver';
6
+
7
+ declare const figma: any;
8
+
9
+ const FONT_LOAD_STATE: Record<string, 'loading' | 'loaded'> = {};
10
+ const FONT_LOAD_PROMISES: Promise<void>[] = [];
11
+
12
+ export function waitForAllFonts(): Promise<void> {
13
+ return Promise.all(FONT_LOAD_PROMISES).then(() => undefined);
14
+ }
15
+
16
+ export const INLINE_TEXT_TAGS = new Set(['span', 'a', 'label', 'small', 'strong', 'em', 'b', 'i']);
17
+
18
+ export const TAG_TYPOGRAPHY: Record<string, { fontSize: number; bold?: boolean; lineHeight?: number }> = {
19
+ h1: { fontSize: 32, bold: true, lineHeight: 40 },
20
+ h2: { fontSize: 24, bold: true, lineHeight: 32 },
21
+ h3: { fontSize: 20, bold: true, lineHeight: 28 },
22
+ h4: { fontSize: 18, bold: true, lineHeight: 24 },
23
+ h5: { fontSize: 16, bold: true, lineHeight: 22 },
24
+ h6: { fontSize: 14, bold: true, lineHeight: 20 },
25
+ p: { fontSize: 14, lineHeight: 20 },
26
+ span: { fontSize: 14 },
27
+ label: { fontSize: 14 },
28
+ a: { fontSize: 14 },
29
+ li: { fontSize: 14 },
30
+ small: { fontSize: 12 },
31
+ };
32
+
33
+ function queueFontLoad(family: string, style: string): void {
34
+ const key = family + ':' + style;
35
+ if (FONT_LOAD_STATE[key]) return;
36
+ FONT_LOAD_STATE[key] = 'loading';
37
+ try {
38
+ const p: Promise<void> = figma.loadFontAsync({ family: family, style: style })
39
+ .then(function() {
40
+ FONT_LOAD_STATE[key] = 'loaded';
41
+ })
42
+ .catch(function() {
43
+ delete FONT_LOAD_STATE[key];
44
+ });
45
+ FONT_LOAD_PROMISES.push(p);
46
+ } catch (_err) {
47
+ delete FONT_LOAD_STATE[key];
48
+ }
49
+ }
50
+
51
+ export function createTextNode(text: string, options?: any): any {
52
+ const node = figma.createText();
53
+ node.characters = text;
54
+ const opts = options || {};
55
+ const style = opts.fontStyle || (opts.bold ? 'Bold' : 'Regular');
56
+ const fontRole = opts.fontRole || 'sans';
57
+ const fontFamily = opts.fontFamily || (opts.theme ? getThemeFontFamily(TOKENS, opts.theme, fontRole) : null) || getCoreFontFamily(TOKENS) || 'Inter';
58
+ const styleCandidates = getFontStyleCandidatesForFamily(fontFamily, style);
59
+ let didSet = false;
60
+ for (const candidate of styleCandidates) {
61
+ const fontKey = fontFamily + ':' + candidate;
62
+ try {
63
+ node.fontName = { family: fontFamily, style: candidate };
64
+ FONT_LOAD_STATE[fontKey] = 'loaded';
65
+ didSet = true;
66
+ break;
67
+ } catch (_err) {
68
+ // try next candidate
69
+ }
70
+ }
71
+ if (!didSet) {
72
+ for (const candidate of styleCandidates) {
73
+ queueFontLoad(fontFamily, candidate);
74
+ }
75
+ }
76
+ if (opts.fontSize) node.fontSize = opts.fontSize;
77
+ if (opts.lineHeight) {
78
+ node.lineHeight = { unit: 'PIXELS', value: opts.lineHeight };
79
+ }
80
+ if (opts.textAlignHorizontal) {
81
+ node.textAlignHorizontal = opts.textAlignHorizontal;
82
+ }
83
+ if (opts.fill) {
84
+ const fill = typeof opts.fill === 'string' ? parseColor(opts.fill) : opts.fill;
85
+ node.fills = [{
86
+ type: 'SOLID',
87
+ color: { r: fill.r, g: fill.g, b: fill.b },
88
+ opacity: opts.opacity != null ? opts.opacity : (fill.a == null ? 1 : fill.a)
89
+ }];
90
+ } else if (opts.opacity != null) {
91
+ node.opacity = opts.opacity;
92
+ }
93
+ return node;
94
+ }
95
+
96
+ export function getNodeTextStyle(node: NodeIR, colorGroup: Record<string, string>): TailwindStyle {
97
+ const classes = isElementLikeNode(node) ? node.classes : [];
98
+ const style = tailwindClassesToStyle(classes, 'default', colorGroup);
99
+ return style;
100
+ }
101
+
102
+ export function getTextAlignFromClasses(classes: string[]): 'LEFT' | 'CENTER' | 'RIGHT' | undefined {
103
+ if (classes.includes('text-center')) return 'CENTER';
104
+ if (classes.includes('text-right')) return 'RIGHT';
105
+ if (classes.includes('text-left')) return 'LEFT';
106
+ return undefined;
107
+ }
108
+
109
+ export function getLineHeightFromClasses(classes: string[], fontSize: number): number | undefined {
110
+ if (classes.includes('leading-none')) return fontSize;
111
+ if (classes.includes('leading-tight')) return Math.round(fontSize * 1.25);
112
+ if (classes.includes('leading-snug')) return Math.round(fontSize * 1.375);
113
+ if (classes.includes('leading-normal')) return Math.round(fontSize * 1.5);
114
+ if (classes.includes('leading-relaxed')) return Math.round(fontSize * 1.625);
115
+ return undefined;
116
+ }
@@ -0,0 +1,42 @@
1
+ import { type NodeIR, type NodeIRComponent, splitClassName } from './node-ir';
2
+ import { parseTextLineCount, parseWidthTokenList, TEXT_LINE_WIDTH_CLASSES } from './render-context';
3
+
4
+ export function buildTextLinePlaceholderNode(node: NodeIRComponent): NodeIR {
5
+ const props = node.props || {};
6
+ const lineCount = parseTextLineCount(props.lines);
7
+ const widths = parseWidthTokenList(props.widths);
8
+ const containerClasses = ['space-y-2'].concat(splitClassName(props.className));
9
+ const lineExtraClasses = splitClassName(props.lineClassName);
10
+
11
+ const children: NodeIR[] = [];
12
+ for (let i = 0; i < lineCount; i++) {
13
+ const widthKey = widths[i % widths.length] || 'full';
14
+ const widthClass = TEXT_LINE_WIDTH_CLASSES[widthKey] || 'w-full';
15
+ const lineClasses = ['bg-gray-200', 'animate-pulse', 'rounded', 'h-4', widthClass].concat(lineExtraClasses);
16
+ children.push({
17
+ kind: 'element',
18
+ tagName: 'div',
19
+ tagLower: 'div',
20
+ props: { className: lineClasses.join(' ') },
21
+ classes: lineClasses,
22
+ children: [],
23
+ });
24
+ }
25
+
26
+ return {
27
+ kind: 'element',
28
+ tagName: 'div',
29
+ tagLower: 'div',
30
+ props: { className: containerClasses.join(' ') },
31
+ classes: containerClasses,
32
+ children: children,
33
+ };
34
+ }
35
+
36
+ export function maybeExpandTextLineComponent(node: NodeIRComponent): NodeIR | null {
37
+ if (!node || (node.children && node.children.length > 0)) return null;
38
+ const props = node.props || {};
39
+ const hasLineProps = props.lines != null || props.widths != null || props.lineClassName != null;
40
+ if (!hasLineProps) return null;
41
+ return buildTextLinePlaceholderNode(node);
42
+ }
@@ -0,0 +1,43 @@
1
+ export type TokenSourceMode = 'auto' | 'css' | 'dtcg';
2
+ export type ResolvedTokenSourceMode = 'css' | 'dtcg' | 'embedded';
3
+
4
+ export interface ScannedThemeTokens {
5
+ colors: Record<string, string>;
6
+ radius?: Record<string, number>;
7
+ fonts?: Record<string, string>;
8
+ spacing?: Record<string, number>;
9
+ fontSize?: Record<string, number>;
10
+ shadows?: Record<string, string>;
11
+ }
12
+
13
+ export interface ScannedTokenMap {
14
+ source: string;
15
+ mode: ResolvedTokenSourceMode;
16
+ requestedMode?: TokenSourceMode;
17
+ colors: Record<string, string>;
18
+ radius: Record<string, number>;
19
+ fonts: Record<string, string>;
20
+ spacing: Record<string, number>;
21
+ fontSize: Record<string, number>;
22
+ shadows: Record<string, string>;
23
+ themes: Record<string, ScannedThemeTokens>;
24
+ }
25
+
26
+ export function createEmptyScannedTokenMap(
27
+ mode: ResolvedTokenSourceMode,
28
+ source: string,
29
+ requestedMode?: TokenSourceMode
30
+ ): ScannedTokenMap {
31
+ return {
32
+ source,
33
+ mode,
34
+ requestedMode,
35
+ colors: {},
36
+ radius: {},
37
+ fonts: {},
38
+ spacing: {},
39
+ fontSize: {},
40
+ shadows: {},
41
+ themes: {},
42
+ };
43
+ }