@styleframe/theme 1.0.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 (60) hide show
  1. package/.tsbuildinfo +1 -0
  2. package/CHANGELOG.md +12 -0
  3. package/package.json +48 -0
  4. package/src/constants/border.ts +0 -0
  5. package/src/constants/breakpoint.ts +0 -0
  6. package/src/constants/color.ts +0 -0
  7. package/src/constants/index.ts +4 -0
  8. package/src/constants/scale.ts +3 -0
  9. package/src/constants/typography.ts +0 -0
  10. package/src/index.ts +4 -0
  11. package/src/shims.d.ts +8 -0
  12. package/src/types.ts +32 -0
  13. package/src/utils/createUseVariable.test.ts +928 -0
  14. package/src/utils/createUseVariable.ts +107 -0
  15. package/src/utils/index.ts +1 -0
  16. package/src/variables/index.ts +22 -0
  17. package/src/variables/useBorderColor.ts +27 -0
  18. package/src/variables/useBorderRadius.test.ts +335 -0
  19. package/src/variables/useBorderRadius.ts +26 -0
  20. package/src/variables/useBorderStyle.test.ts +569 -0
  21. package/src/variables/useBorderStyle.ts +49 -0
  22. package/src/variables/useBorderWidth.test.ts +535 -0
  23. package/src/variables/useBorderWidth.ts +38 -0
  24. package/src/variables/useBoxShadow.test.ts +336 -0
  25. package/src/variables/useBoxShadow.ts +54 -0
  26. package/src/variables/useBreakpoint.test.ts +447 -0
  27. package/src/variables/useBreakpoint.ts +38 -0
  28. package/src/variables/useColor.test.ts +360 -0
  29. package/src/variables/useColor.ts +35 -0
  30. package/src/variables/useColorLightness.test.ts +168 -0
  31. package/src/variables/useColorLightness.ts +59 -0
  32. package/src/variables/useColorShade.test.ts +166 -0
  33. package/src/variables/useColorShade.ts +52 -0
  34. package/src/variables/useColorTint.test.ts +164 -0
  35. package/src/variables/useColorTint.ts +52 -0
  36. package/src/variables/useEasing.ts +0 -0
  37. package/src/variables/useFontFamily.test.ts +228 -0
  38. package/src/variables/useFontFamily.ts +33 -0
  39. package/src/variables/useFontSize.test.ts +299 -0
  40. package/src/variables/useFontSize.ts +32 -0
  41. package/src/variables/useFontStyle.test.ts +555 -0
  42. package/src/variables/useFontStyle.ts +37 -0
  43. package/src/variables/useFontWeight.test.ts +650 -0
  44. package/src/variables/useFontWeight.ts +55 -0
  45. package/src/variables/useLetterSpacing.test.ts +455 -0
  46. package/src/variables/useLetterSpacing.ts +41 -0
  47. package/src/variables/useLineHeight.test.ts +410 -0
  48. package/src/variables/useLineHeight.ts +41 -0
  49. package/src/variables/useMultiplier.test.ts +722 -0
  50. package/src/variables/useMultiplier.ts +44 -0
  51. package/src/variables/useScale.test.ts +393 -0
  52. package/src/variables/useScale.ts +52 -0
  53. package/src/variables/useScalePowers.test.ts +412 -0
  54. package/src/variables/useScalePowers.ts +35 -0
  55. package/src/variables/useSpacing.test.ts +309 -0
  56. package/src/variables/useSpacing.ts +26 -0
  57. package/src/vite-env.d.ts +1 -0
  58. package/test-scale.js +22 -0
  59. package/tsconfig.json +7 -0
  60. package/vite.config.ts +5 -0
@@ -0,0 +1,412 @@
1
+ import type { TokenValue } from "@styleframe/core";
2
+ import { styleframe } from "@styleframe/core";
3
+ import { consume } from "@styleframe/transpiler";
4
+ import { defaultScalePowerValues } from "../constants/scale";
5
+ import { useScale } from "./useScale";
6
+ import { useScalePowers } from "./useScalePowers";
7
+
8
+ describe("useScalePowers", () => {
9
+ it("should create scale powers with default powers array", () => {
10
+ const s = styleframe();
11
+ const { scaleGolden } = useScale(s);
12
+ const powers = useScalePowers(s, scaleGolden, defaultScalePowerValues);
13
+
14
+ // Keys are in insertion order
15
+ expect(Object.keys(powers).length).toBe(8);
16
+ expect(powers[-2]).toBeDefined();
17
+ expect(powers[-1]).toBeDefined();
18
+ expect(powers[0]).toBeDefined();
19
+ expect(powers[1]).toBeDefined();
20
+ expect(powers[2]).toBeDefined();
21
+ expect(powers[3]).toBeDefined();
22
+ expect(powers[4]).toBeDefined();
23
+ expect(powers[5]).toBeDefined();
24
+ });
25
+
26
+ it("should create CSS templates for each power", () => {
27
+ const s = styleframe();
28
+ const { scaleMajorThird } = useScale(s);
29
+ const powers = useScalePowers(s, scaleMajorThird, [1, 2, 3]);
30
+
31
+ expect(powers[1]).toHaveProperty("type", "css");
32
+ expect(powers[2]).toHaveProperty("type", "css");
33
+ expect(powers[3]).toHaveProperty("type", "css");
34
+ });
35
+
36
+ it("should handle custom powers array", () => {
37
+ const s = styleframe();
38
+ const { scaleGolden } = useScale(s);
39
+ const powers = useScalePowers(s, scaleGolden, [0, 1, 10]);
40
+
41
+ expect(Object.keys(powers).length).toBe(3);
42
+ expect(powers[0]).toBeDefined();
43
+ expect(powers[1]).toBeDefined();
44
+ expect(powers[10]).toBeDefined();
45
+ });
46
+
47
+ it("should handle single power", () => {
48
+ const s = styleframe();
49
+ const { scaleMajorSecond } = useScale(s);
50
+ const powers = useScalePowers(s, scaleMajorSecond, [2]);
51
+
52
+ expect(Object.keys(powers)).toEqual(["2"]);
53
+ expect(powers[2]).toBeDefined();
54
+ });
55
+
56
+ it("should handle empty powers array", () => {
57
+ const s = styleframe();
58
+ const { scaleMinorThird } = useScale(s);
59
+ const powers = useScalePowers(s, scaleMinorThird, []);
60
+
61
+ expect(Object.keys(powers)).toEqual([]);
62
+ expect(powers).toEqual({});
63
+ });
64
+
65
+ it("should work with different scale variables", () => {
66
+ const s = styleframe();
67
+ const { scaleMinorSecond, scaleMajorSecond, scaleGolden } = useScale(s);
68
+
69
+ const powers1 = useScalePowers(s, scaleMinorSecond, [1, 2]);
70
+ const powers2 = useScalePowers(s, scaleMajorSecond, [1, 2]);
71
+ const powers3 = useScalePowers(s, scaleGolden, [1, 2]);
72
+
73
+ expect(powers1[1]).toBeDefined();
74
+ expect(powers2[1]).toBeDefined();
75
+ expect(powers3[1]).toBeDefined();
76
+ });
77
+
78
+ it("should compile to correct CSS with positive powers", () => {
79
+ const s = styleframe();
80
+ const { scaleMajorThird } = useScale(s);
81
+ const powers = useScalePowers(s, scaleMajorThird, [1, 2] as const);
82
+
83
+ const baseSize = s.variable("base", "1rem");
84
+ s.selector(".scale-1", ({ variable }) => {
85
+ variable("font-size", s.css`calc(${s.ref(baseSize)} * ${powers[1]})`);
86
+ });
87
+ s.selector(".scale-2", ({ variable }) => {
88
+ variable("font-size", s.css`calc(${s.ref(baseSize)} * (${powers[2]}))`);
89
+ });
90
+
91
+ const css = consume(s.root, s.options);
92
+
93
+ expect(css).toEqual(`:root {
94
+ --scale--minor-second: 1.067;
95
+ --scale--major-second: 1.125;
96
+ --scale--minor-third: 1.2;
97
+ --scale--major-third: 1.25;
98
+ --scale--perfect-fourth: 1.333;
99
+ --scale--augmented-fourth: 1.414;
100
+ --scale--perfect-fifth: 1.5;
101
+ --scale--golden: 1.618;
102
+ --scale: var(--scale--minor-third);
103
+ --base: 1rem;
104
+ }
105
+
106
+ .scale-1 {
107
+ --font-size: calc(var(--base) * var(--scale--major-third));
108
+ }
109
+
110
+ .scale-2 {
111
+ --font-size: calc(var(--base) * (var(--scale--major-third) * var(--scale--major-third)));
112
+ }`);
113
+ });
114
+
115
+ it("should compile to correct CSS with negative powers", () => {
116
+ const s = styleframe();
117
+ const { scaleGolden } = useScale(s);
118
+ const powers = useScalePowers(s, scaleGolden, [-1, -2] as const);
119
+
120
+ const baseSize = s.variable("base", "1rem");
121
+ s.selector(".scale-neg-1", ({ variable }) => {
122
+ variable("font-size", s.css`calc(${s.ref(baseSize)} * ${powers[-1]})`);
123
+ });
124
+ s.selector(".scale-neg-2", ({ variable }) => {
125
+ variable("font-size", s.css`calc(${s.ref(baseSize)} * ${powers[-2]})`);
126
+ });
127
+
128
+ const css = consume(s.root, s.options);
129
+
130
+ expect(css).toEqual(`:root {
131
+ --scale--minor-second: 1.067;
132
+ --scale--major-second: 1.125;
133
+ --scale--minor-third: 1.2;
134
+ --scale--major-third: 1.25;
135
+ --scale--perfect-fourth: 1.333;
136
+ --scale--augmented-fourth: 1.414;
137
+ --scale--perfect-fifth: 1.5;
138
+ --scale--golden: 1.618;
139
+ --scale: var(--scale--minor-third);
140
+ --base: 1rem;
141
+ }
142
+
143
+ .scale-neg-1 {
144
+ --font-size: calc(var(--base) * 1 / var(--scale--golden));
145
+ }
146
+
147
+ .scale-neg-2 {
148
+ --font-size: calc(var(--base) * 1 / var(--scale--golden) / var(--scale--golden));
149
+ }`);
150
+ });
151
+
152
+ it("should handle mixed positive and negative powers", () => {
153
+ const s = styleframe();
154
+ const { scalePerfectFourth } = useScale(s);
155
+ const powers = useScalePowers(s, scalePerfectFourth, [
156
+ -3, -1, 1, 3,
157
+ ] as const);
158
+
159
+ expect(Object.keys(powers).length).toBe(4);
160
+ expect(powers[-3]).toBeDefined();
161
+ expect(powers[-1]).toBeDefined();
162
+ expect(powers[1]).toBeDefined();
163
+ expect(powers[3]).toBeDefined();
164
+ });
165
+
166
+ describe("type safety", () => {
167
+ it("should return Record<number, TokenValue>", () => {
168
+ const s = styleframe();
169
+ const { scaleGolden } = useScale(s);
170
+ const powers = useScalePowers(s, scaleGolden, [1, 2, 3] as const);
171
+
172
+ // Type check
173
+ const result: Record<number, TokenValue> = powers;
174
+ expect(result).toBeDefined();
175
+ });
176
+
177
+ it("should accept any Variable as scale parameter", () => {
178
+ const s = styleframe();
179
+ const customScale = s.variable("custom-scale", 1.5);
180
+ const powers = useScalePowers(s, customScale, [1, 2] as const);
181
+
182
+ expect(powers[1]).toBeDefined();
183
+ expect(powers[2]).toBeDefined();
184
+ });
185
+ });
186
+
187
+ describe("practical usage", () => {
188
+ it("should create modular typography scale", () => {
189
+ const s = styleframe();
190
+ const { scale } = useScale(s);
191
+
192
+ const powers = useScalePowers(s, scale, [-2, -1, 1, 2, 3] as const);
193
+
194
+ const fontSize = s.variable("font-size", "1rem");
195
+
196
+ s.variable(
197
+ "font-size-xs",
198
+ s.css`calc(${s.ref(fontSize)} * ${powers[-2]})`,
199
+ );
200
+ s.variable(
201
+ "font-size-sm",
202
+ s.css`calc(${s.ref(fontSize)} * ${powers[-1]})`,
203
+ );
204
+ s.variable("font-size-md", s.ref(fontSize));
205
+ s.variable(
206
+ "font-size-lg",
207
+ s.css`calc(${s.ref(fontSize)} * ${powers[1]})`,
208
+ );
209
+ s.variable(
210
+ "font-size-xl",
211
+ s.css`calc(${s.ref(fontSize)} * ${powers[2]})`,
212
+ );
213
+ s.variable(
214
+ "font-size-2xl",
215
+ s.css`calc(${s.ref(fontSize)} * ${powers[3]})`,
216
+ );
217
+
218
+ const css = consume(s.root, s.options);
219
+
220
+ expect(css).toEqual(`:root {
221
+ --scale--minor-second: 1.067;
222
+ --scale--major-second: 1.125;
223
+ --scale--minor-third: 1.2;
224
+ --scale--major-third: 1.25;
225
+ --scale--perfect-fourth: 1.333;
226
+ --scale--augmented-fourth: 1.414;
227
+ --scale--perfect-fifth: 1.5;
228
+ --scale--golden: 1.618;
229
+ --scale: var(--scale--minor-third);
230
+ --font-size: 1rem;
231
+ --font-size-xs: calc(var(--font-size) * 1 / var(--scale) / var(--scale));
232
+ --font-size-sm: calc(var(--font-size) * 1 / var(--scale));
233
+ --font-size-md: var(--font-size);
234
+ --font-size-lg: calc(var(--font-size) * var(--scale));
235
+ --font-size-xl: calc(var(--font-size) * var(--scale) * var(--scale));
236
+ --font-size-2xl: calc(var(--font-size) * var(--scale) * var(--scale) * var(--scale));
237
+ }`);
238
+ });
239
+
240
+ it("should create spacing scale with golden ratio", () => {
241
+ const s = styleframe();
242
+ const { scaleGolden } = useScale(s);
243
+ const powers = useScalePowers(s, scaleGolden, [-1, 0, 1, 2]);
244
+
245
+ const baseSpacing = s.variable("spacing-base", "1rem");
246
+ s.variable(
247
+ "spacing-sm",
248
+ s.css`calc(${s.ref(baseSpacing)} * ${powers[-1]})`,
249
+ );
250
+ s.variable(
251
+ "spacing-md",
252
+ s.css`calc(${s.ref(baseSpacing)} * ${powers[0]})`,
253
+ );
254
+ s.variable(
255
+ "spacing-lg",
256
+ s.css`calc(${s.ref(baseSpacing)} * ${powers[1]})`,
257
+ );
258
+ s.variable(
259
+ "spacing-xl",
260
+ s.css`calc(${s.ref(baseSpacing)} * ${powers[2]})`,
261
+ );
262
+
263
+ const css = consume(s.root, s.options);
264
+
265
+ expect(css).toEqual(`:root {
266
+ --scale--minor-second: 1.067;
267
+ --scale--major-second: 1.125;
268
+ --scale--minor-third: 1.2;
269
+ --scale--major-third: 1.25;
270
+ --scale--perfect-fourth: 1.333;
271
+ --scale--augmented-fourth: 1.414;
272
+ --scale--perfect-fifth: 1.5;
273
+ --scale--golden: 1.618;
274
+ --scale: var(--scale--minor-third);
275
+ --spacing-base: 1rem;
276
+ --spacing-sm: calc(var(--spacing-base) * 1 / var(--scale--golden));
277
+ --spacing-md: calc(var(--spacing-base) * 1);
278
+ --spacing-lg: calc(var(--spacing-base) * var(--scale--golden));
279
+ --spacing-xl: calc(var(--spacing-base) * var(--scale--golden) * var(--scale--golden));
280
+ }`);
281
+ });
282
+
283
+ it("should work with default scale powers constant", () => {
284
+ const s = styleframe();
285
+ const { scaleMajorSecond } = useScale(s);
286
+ const powers = useScalePowers(
287
+ s,
288
+ scaleMajorSecond,
289
+ defaultScalePowerValues,
290
+ );
291
+
292
+ // defaultScalePowerValues = [-2, -1, 1, 2, 3, 4, 5]
293
+ expect(Object.keys(powers).length).toBe(defaultScalePowerValues.length);
294
+ expect(powers[-2]).toBeDefined();
295
+ expect(powers[-1]).toBeDefined();
296
+ expect(powers[1]).toBeDefined();
297
+ expect(powers[2]).toBeDefined();
298
+ expect(powers[3]).toBeDefined();
299
+ expect(powers[4]).toBeDefined();
300
+ expect(powers[5]).toBeDefined();
301
+ });
302
+
303
+ it("should work with different scale ratios", () => {
304
+ const s = styleframe();
305
+ const { scaleMajorSecond, scaleGolden } = useScale(s);
306
+
307
+ const mobilePowers = useScalePowers(s, scaleMajorSecond, [1, 2, 3]);
308
+ const desktopPowers = useScalePowers(s, scaleGolden, [1, 2, 3]);
309
+
310
+ const baseSize = s.variable("size-base", "1rem");
311
+
312
+ s.selector(".mobile", ({ variable }) => {
313
+ variable(
314
+ "font-size",
315
+ s.css`calc(${s.ref(baseSize)} * ${mobilePowers[2]})`,
316
+ );
317
+ });
318
+
319
+ s.selector(".desktop", ({ variable }) => {
320
+ variable(
321
+ "font-size",
322
+ s.css`calc(${s.ref(baseSize)} * ${desktopPowers[2]})`,
323
+ );
324
+ });
325
+
326
+ const css = consume(s.root, s.options);
327
+
328
+ expect(css).toEqual(`:root {
329
+ --scale--minor-second: 1.067;
330
+ --scale--major-second: 1.125;
331
+ --scale--minor-third: 1.2;
332
+ --scale--major-third: 1.25;
333
+ --scale--perfect-fourth: 1.333;
334
+ --scale--augmented-fourth: 1.414;
335
+ --scale--perfect-fifth: 1.5;
336
+ --scale--golden: 1.618;
337
+ --scale: var(--scale--minor-third);
338
+ --size-base: 1rem;
339
+ }
340
+
341
+ .mobile {
342
+ --font-size: calc(var(--size-base) * var(--scale--major-second) * var(--scale--major-second));
343
+ }
344
+
345
+ .desktop {
346
+ --font-size: calc(var(--size-base) * var(--scale--golden) * var(--scale--golden));
347
+ }`);
348
+ });
349
+ });
350
+
351
+ describe("edge cases", () => {
352
+ it("should handle power of 0", () => {
353
+ const s = styleframe();
354
+ const { scaleMajorThird } = useScale(s);
355
+ const powers = useScalePowers(s, scaleMajorThird, [0]);
356
+
357
+ expect(powers[0]).toBeDefined();
358
+ expect(powers[0]).toHaveProperty("type", "css");
359
+ });
360
+
361
+ it("should handle duplicate powers in array", () => {
362
+ const s = styleframe();
363
+ const { scaleMinorSecond } = useScale(s);
364
+ const powers = useScalePowers(s, scaleMinorSecond, [1, 1, 2, 2]);
365
+
366
+ // Should overwrite duplicates, resulting in unique keys
367
+ expect(Object.keys(powers).length).toBe(2);
368
+ expect(powers[1]).toBeDefined();
369
+ expect(powers[2]).toBeDefined();
370
+ });
371
+
372
+ it("should handle unsorted powers array", () => {
373
+ const s = styleframe();
374
+ const { scalePerfectFifth } = useScale(s);
375
+ const powers = useScalePowers(s, scalePerfectFifth, [3, -1, 2, -2, 1]);
376
+
377
+ expect(Object.keys(powers).length).toBe(5);
378
+ expect(powers[-2]).toBeDefined();
379
+ expect(powers[-1]).toBeDefined();
380
+ expect(powers[1]).toBeDefined();
381
+ expect(powers[2]).toBeDefined();
382
+ expect(powers[3]).toBeDefined();
383
+ });
384
+
385
+ it("should handle power of 1", () => {
386
+ const s = styleframe();
387
+ const { scaleAugmentedFourth } = useScale(s);
388
+ const powers = useScalePowers(s, scaleAugmentedFourth, [1]);
389
+
390
+ expect(powers[1]).toBeDefined();
391
+ expect(powers[1]).toHaveProperty("type", "css");
392
+ });
393
+
394
+ it("should handle power of -1", () => {
395
+ const s = styleframe();
396
+ const { scaleAugmentedFourth } = useScale(s);
397
+ const powers = useScalePowers(s, scaleAugmentedFourth, [-1]);
398
+
399
+ expect(powers[-1]).toBeDefined();
400
+ expect(powers[-1]).toHaveProperty("type", "css");
401
+ });
402
+
403
+ it("should handle large powers", () => {
404
+ const s = styleframe();
405
+ const { scaleMajorSecond } = useScale(s);
406
+ const powers = useScalePowers(s, scaleMajorSecond, [10, -10]);
407
+
408
+ expect(powers[10]).toBeDefined();
409
+ expect(powers[-10]).toBeDefined();
410
+ });
411
+ });
412
+ });
@@ -0,0 +1,35 @@
1
+ import type { CSS, Styleframe, TokenValue, Variable } from "@styleframe/core";
2
+ import { defaultScalePowerValues } from "../constants";
3
+
4
+ export function useScalePowers<T extends readonly number[]>(
5
+ s: Styleframe,
6
+ scale: Variable,
7
+ powers: T = defaultScalePowerValues as T,
8
+ ): Record<number, TokenValue> {
9
+ const results: Record<number, CSS> = {};
10
+
11
+ for (const power of powers) {
12
+ const absPower = Math.abs(power);
13
+ const operator = power > 0 ? " * " : " / ";
14
+ const value: TokenValue[] = [];
15
+
16
+ if (power <= 0) {
17
+ value.push("1");
18
+ }
19
+
20
+ // Build the CSS value array directly
21
+ for (let i = 0; i < absPower; i++) {
22
+ if (i > 0 || power < 0) {
23
+ value.push(operator);
24
+ }
25
+ value.push(s.ref(scale));
26
+ }
27
+
28
+ results[power] = {
29
+ type: "css",
30
+ value,
31
+ };
32
+ }
33
+
34
+ return results as Record<keyof typeof powers, TokenValue>;
35
+ }