@primitiv-ui/tokens 0.1.21 → 0.1.22

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 CHANGED
@@ -17,6 +17,7 @@ token transformer (e.g. CSS variables, Tailwind config) reads from.
17
17
  | `src/index.ts` | Public entry; re-exports the transform, the server, and types |
18
18
  | `src/*.test.ts` | Vitest unit tests, 100% coverage |
19
19
  | `src/{primitives,palette,intent,context,interaction}.json` | DTCG output written by the sync server. **Committed** — these files are the repo's source of truth for downstream transformers. |
20
+ | `src/motion.json` | **Code-only** DTCG (motion durations + easing curves + the semantic `motion.*` layer). Hand-authored, *not* a sync target — Figma has no `cubicBezier` variable type, so motion can't round-trip. Sits outside the five-file write-set, so a backup never touches it. |
20
21
 
21
22
  ## Backing up tokens (end-to-end)
22
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitiv-ui/tokens",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "The DTCG-conformant design-token layer for the Primitiv ecosystem, plus helpers that transform Figma variable exports into DTCG JSON.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/dtcg.test.ts CHANGED
@@ -479,6 +479,41 @@ describe('figmaVarsToDtcg', () => {
479
479
  })
480
480
  })
481
481
 
482
+ it('merges two modes that collapse to the same top-level key', () => {
483
+ // Mode names are lowercased before they become keys, so 'Light' and 'light'
484
+ // both target intent.light. The second merge must reuse the existing group
485
+ // rather than recreate it — exercising the "key already present" path.
486
+ const DUP_MODE_COLL: FigmaCollection = {
487
+ id: 'cdup',
488
+ name: 'Intent',
489
+ modes: [
490
+ { modeId: 'mL', name: 'Light' },
491
+ { modeId: 'ml', name: 'light' },
492
+ ],
493
+ defaultModeId: 'mL',
494
+ }
495
+ const result = figmaVarsToDtcg(
496
+ [DUP_MODE_COLL],
497
+ [
498
+ {
499
+ id: 'v1',
500
+ name: 'action/primary/default',
501
+ resolvedType: 'COLOR',
502
+ variableCollectionId: 'cdup',
503
+ valuesByMode: {
504
+ mL: { r: 0, g: 0, b: 0, a: 1 },
505
+ ml: { r: 1, g: 1, b: 1, a: 1 },
506
+ },
507
+ },
508
+ ],
509
+ )
510
+
511
+ // The later 'light' mode wins, proving its merge landed in the existing key.
512
+ expect(result.intent.light).toEqual({
513
+ action: { primary: { default: { $type: 'color', $value: '#ffffff' } } },
514
+ })
515
+ })
516
+
482
517
  it('routes Primitives / Foreground per-mode into foreground, resolving aliases', () => {
483
518
  const result = figmaVarsToDtcg(
484
519
  [FOREGROUND_COLL],
package/src/dtcg.ts CHANGED
@@ -128,12 +128,13 @@ export function figmaVarsToDtcg(
128
128
  const foreground = collections.find((c) => c.name === 'Primitives / Foreground')
129
129
  const intent = collections.find((c) => c.name === 'Intent')
130
130
  const context = collections.find((c) => c.name === 'Context')
131
- const singleMode = collections.filter(
132
- (c) => c.name === 'Primitives' || c.name === 'Interaction',
133
- )
131
+ const singleMode = collections.filter((c) => c.name in SINGLE_MODE_FILES)
134
132
 
135
133
  const routes = new Map<string, Routing>(
136
- singleMode.map((c) => [c.id, routeCollection(c.name)]),
134
+ singleMode.map((c) => [
135
+ c.id,
136
+ { file: SINGLE_MODE_FILES[c.name], prefix: [] },
137
+ ]),
137
138
  )
138
139
 
139
140
  // All variables are addressable by their natural name path — no collection prefix.
@@ -200,10 +201,14 @@ export function figmaVarsToDtcg(
200
201
  return files
201
202
  }
202
203
 
203
- function routeCollection(name: string): Routing {
204
- if (name === 'Primitives') return { file: 'primitives', prefix: [] }
205
- if (name === 'Interaction') return { file: 'interaction', prefix: [] }
206
- throw new Error(`Unrecognised collection name: "${name}"`)
204
+ /**
205
+ * Single-mode collection names mapped to their output file. The keys are the
206
+ * single source of truth for both the `singleMode` filter and the routing
207
+ * lookup, so the two can never drift apart.
208
+ */
209
+ const SINGLE_MODE_FILES: Record<string, keyof DtcgFiles> = {
210
+ Primitives: 'primitives',
211
+ Interaction: 'interaction',
207
212
  }
208
213
 
209
214
  function mergeIntoPrefix(
@@ -0,0 +1,149 @@
1
+ {
2
+ "shadow": {
3
+ "color": {
4
+ "strong": {
5
+ "$type": "color",
6
+ "$value": "#00000014"
7
+ },
8
+ "medium": {
9
+ "$type": "color",
10
+ "$value": "#0000000f"
11
+ },
12
+ "soft": {
13
+ "$type": "color",
14
+ "$value": "#0000000a"
15
+ }
16
+ },
17
+ "1": {
18
+ "$type": "shadow",
19
+ "$value": [
20
+ {
21
+ "color": "{shadow.color.strong}",
22
+ "offsetX": "{space.space-0}",
23
+ "offsetY": "{space.space-1}",
24
+ "blur": "{space.space-2}",
25
+ "spread": "{space.space-0}"
26
+ }
27
+ ]
28
+ },
29
+ "2": {
30
+ "$type": "shadow",
31
+ "$value": [
32
+ {
33
+ "color": "{shadow.color.strong}",
34
+ "offsetX": "{space.space-0}",
35
+ "offsetY": "{space.space-1}",
36
+ "blur": "{space.space-2}",
37
+ "spread": "{space.space-0}"
38
+ },
39
+ {
40
+ "color": "{shadow.color.soft}",
41
+ "offsetX": "{space.space-0}",
42
+ "offsetY": "{space.space-2}",
43
+ "blur": "{space.space-4}",
44
+ "spread": "{space.space-0}"
45
+ }
46
+ ]
47
+ },
48
+ "3": {
49
+ "$type": "shadow",
50
+ "$value": [
51
+ {
52
+ "color": "{shadow.color.strong}",
53
+ "offsetX": "{space.space-0}",
54
+ "offsetY": "{space.space-2}",
55
+ "blur": "{space.space-4}",
56
+ "spread": "{space.space-0}"
57
+ },
58
+ {
59
+ "color": "{shadow.color.medium}",
60
+ "offsetX": "{space.space-0}",
61
+ "offsetY": "{space.space-4}",
62
+ "blur": "{space.space-8}",
63
+ "spread": "{space.space-0}"
64
+ },
65
+ {
66
+ "color": "{shadow.color.soft}",
67
+ "offsetX": "{space.space-0}",
68
+ "offsetY": "{space.space-8}",
69
+ "blur": "{space.space-16}",
70
+ "spread": "{space.space-0}"
71
+ }
72
+ ]
73
+ },
74
+ "4": {
75
+ "$type": "shadow",
76
+ "$value": [
77
+ {
78
+ "color": "{shadow.color.strong}",
79
+ "offsetX": "{space.space-0}",
80
+ "offsetY": "{space.space-4}",
81
+ "blur": "{space.space-8}",
82
+ "spread": "{space.space-0}"
83
+ },
84
+ {
85
+ "color": "{shadow.color.medium}",
86
+ "offsetX": "{space.space-0}",
87
+ "offsetY": "{space.space-8}",
88
+ "blur": "{space.space-16}",
89
+ "spread": "{space.space-0}"
90
+ },
91
+ {
92
+ "color": "{shadow.color.soft}",
93
+ "offsetX": "{space.space-0}",
94
+ "offsetY": "{space.space-16}",
95
+ "blur": "{space.space-24}",
96
+ "spread": "{space.space-0}"
97
+ }
98
+ ]
99
+ },
100
+ "5": {
101
+ "$type": "shadow",
102
+ "$value": [
103
+ {
104
+ "color": "{shadow.color.strong}",
105
+ "offsetX": "{space.space-0}",
106
+ "offsetY": "{space.space-8}",
107
+ "blur": "{space.space-16}",
108
+ "spread": "{space.space-0}"
109
+ },
110
+ {
111
+ "color": "{shadow.color.medium}",
112
+ "offsetX": "{space.space-0}",
113
+ "offsetY": "{space.space-16}",
114
+ "blur": "{space.space-32}",
115
+ "spread": "{space.space-0}"
116
+ },
117
+ {
118
+ "color": "{shadow.color.soft}",
119
+ "offsetX": "{space.space-0}",
120
+ "offsetY": "{space.space-24}",
121
+ "blur": "{space.space-48}",
122
+ "spread": "{space.space-0}"
123
+ }
124
+ ]
125
+ }
126
+ },
127
+ "elevation": {
128
+ "flat": {
129
+ "$type": "shadow",
130
+ "$value": []
131
+ },
132
+ "raised": {
133
+ "$type": "shadow",
134
+ "$value": "{shadow.2}"
135
+ },
136
+ "overlay": {
137
+ "$type": "shadow",
138
+ "$value": "{shadow.3}"
139
+ },
140
+ "floating": {
141
+ "$type": "shadow",
142
+ "$value": "{shadow.4}"
143
+ },
144
+ "modal": {
145
+ "$type": "shadow",
146
+ "$value": "{shadow.5}"
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "duration": {
3
+ "0": {
4
+ "$type": "number",
5
+ "$value": 0
6
+ },
7
+ "75": {
8
+ "$type": "number",
9
+ "$value": 75
10
+ },
11
+ "100": {
12
+ "$type": "number",
13
+ "$value": 100
14
+ },
15
+ "150": {
16
+ "$type": "number",
17
+ "$value": 150
18
+ },
19
+ "200": {
20
+ "$type": "number",
21
+ "$value": 200
22
+ },
23
+ "300": {
24
+ "$type": "number",
25
+ "$value": 300
26
+ },
27
+ "500": {
28
+ "$type": "number",
29
+ "$value": 500
30
+ },
31
+ "700": {
32
+ "$type": "number",
33
+ "$value": 700
34
+ },
35
+ "1000": {
36
+ "$type": "number",
37
+ "$value": 1000
38
+ }
39
+ },
40
+ "easing": {
41
+ "linear": {
42
+ "$type": "cubicBezier",
43
+ "$value": [0, 0, 1, 1]
44
+ },
45
+ "in": {
46
+ "$type": "cubicBezier",
47
+ "$value": [0.4, 0, 1, 1]
48
+ },
49
+ "out": {
50
+ "$type": "cubicBezier",
51
+ "$value": [0, 0, 0.2, 1]
52
+ },
53
+ "in-out": {
54
+ "$type": "cubicBezier",
55
+ "$value": [0.4, 0, 0.2, 1]
56
+ }
57
+ },
58
+ "motion": {
59
+ "duration": {
60
+ "feedback": {
61
+ "$type": "number",
62
+ "$value": "{duration.75}"
63
+ },
64
+ "control": {
65
+ "$type": "number",
66
+ "$value": "{duration.150}"
67
+ },
68
+ "expand": {
69
+ "$type": "number",
70
+ "$value": "{duration.200}"
71
+ },
72
+ "overlay": {
73
+ "$type": "number",
74
+ "$value": "{duration.300}"
75
+ }
76
+ },
77
+ "easing": {
78
+ "default": {
79
+ "$type": "cubicBezier",
80
+ "$value": "{easing.in-out}"
81
+ },
82
+ "enter": {
83
+ "$type": "cubicBezier",
84
+ "$value": "{easing.out}"
85
+ },
86
+ "exit": {
87
+ "$type": "cubicBezier",
88
+ "$value": "{easing.in}"
89
+ },
90
+ "linear": {
91
+ "$type": "cubicBezier",
92
+ "$value": "{easing.linear}"
93
+ }
94
+ }
95
+ }
96
+ }