@discourser/design-system 0.15.1 → 0.18.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 (281) hide show
  1. package/dist/{chunk-UNWXE6UB.cjs → chunk-2P7Z5PVP.cjs} +817 -16
  2. package/dist/chunk-2P7Z5PVP.cjs.map +1 -0
  3. package/dist/{chunk-ABC7N32K.cjs → chunk-PFWU7QSM.cjs} +464 -8
  4. package/dist/chunk-PFWU7QSM.cjs.map +1 -0
  5. package/dist/{chunk-GD6Q2FUE.js → chunk-QC7LGFM3.js} +808 -18
  6. package/dist/chunk-QC7LGFM3.js.map +1 -0
  7. package/dist/{chunk-SBKRSXSZ.js → chunk-SNUJBT5R.js} +464 -8
  8. package/dist/chunk-SNUJBT5R.js.map +1 -0
  9. package/dist/components/Accordion.figma.d.ts +2 -0
  10. package/dist/components/Accordion.figma.d.ts.map +1 -0
  11. package/dist/components/Breadcrumb.d.ts +2 -0
  12. package/dist/components/Breadcrumb.d.ts.map +1 -1
  13. package/dist/components/Breadcrumb.figma.d.ts +2 -0
  14. package/dist/components/Breadcrumb.figma.d.ts.map +1 -0
  15. package/dist/components/ContentCard/ContentCard.d.ts +13 -0
  16. package/dist/components/ContentCard/ContentCard.d.ts.map +1 -0
  17. package/dist/components/ContentCard/ContentCard.figma.d.ts +2 -0
  18. package/dist/components/ContentCard/ContentCard.figma.d.ts.map +1 -0
  19. package/dist/components/ContentCard/index.d.ts +2 -0
  20. package/dist/components/ContentCard/index.d.ts.map +1 -0
  21. package/dist/components/{Heading.d.ts → Header.d.ts} +3 -3
  22. package/dist/components/Header.d.ts.map +1 -0
  23. package/dist/components/Header.figma.d.ts +2 -0
  24. package/dist/components/Header.figma.d.ts.map +1 -0
  25. package/dist/components/Icons/AccountIcon.d.ts +6 -0
  26. package/dist/components/Icons/AccountIcon.d.ts.map +1 -0
  27. package/dist/components/Icons/AudienceIcon.d.ts +6 -0
  28. package/dist/components/Icons/AudienceIcon.d.ts.map +1 -0
  29. package/dist/components/Icons/AudienceIcon.figma.d.ts +2 -0
  30. package/dist/components/Icons/AudienceIcon.figma.d.ts.map +1 -0
  31. package/dist/components/Icons/AudioSpeakerIcon.d.ts +6 -0
  32. package/dist/components/Icons/AudioSpeakerIcon.d.ts.map +1 -0
  33. package/dist/components/Icons/AudioSpeakerIcon.figma.d.ts +2 -0
  34. package/dist/components/Icons/AudioSpeakerIcon.figma.d.ts.map +1 -0
  35. package/dist/components/Icons/BookmarkPlusIcon.d.ts +6 -0
  36. package/dist/components/Icons/BookmarkPlusIcon.d.ts.map +1 -0
  37. package/dist/components/Icons/BookmarkPlusIcon.figma.d.ts +2 -0
  38. package/dist/components/Icons/BookmarkPlusIcon.figma.d.ts.map +1 -0
  39. package/dist/components/Icons/ChevronUpIcon.d.ts +6 -0
  40. package/dist/components/Icons/ChevronUpIcon.d.ts.map +1 -0
  41. package/dist/components/Icons/ClipBoardIcon.d.ts +6 -0
  42. package/dist/components/Icons/ClipBoardIcon.d.ts.map +1 -0
  43. package/dist/components/Icons/ClipBoardIcon.figma.d.ts +2 -0
  44. package/dist/components/Icons/ClipBoardIcon.figma.d.ts.map +1 -0
  45. package/dist/components/Icons/ClockIcon.d.ts.map +1 -1
  46. package/dist/components/Icons/DashboardIcon.d.ts +6 -0
  47. package/dist/components/Icons/DashboardIcon.d.ts.map +1 -0
  48. package/dist/components/Icons/DiscourserLogo.d.ts +6 -0
  49. package/dist/components/Icons/DiscourserLogo.d.ts.map +1 -0
  50. package/dist/components/Icons/DiscourserLogo.figma.d.ts +2 -0
  51. package/dist/components/Icons/DiscourserLogo.figma.d.ts.map +1 -0
  52. package/dist/components/Icons/ExitStudioIcon.d.ts +6 -0
  53. package/dist/components/Icons/ExitStudioIcon.d.ts.map +1 -0
  54. package/dist/components/Icons/ExitStudioIcon.figma.d.ts +2 -0
  55. package/dist/components/Icons/ExitStudioIcon.figma.d.ts.map +1 -0
  56. package/dist/components/Icons/GripDotsVerticalIcon.d.ts.map +1 -1
  57. package/dist/components/Icons/HelpIcon.d.ts +6 -0
  58. package/dist/components/Icons/HelpIcon.d.ts.map +1 -0
  59. package/dist/components/Icons/MicrophoneIcon.d.ts +6 -0
  60. package/dist/components/Icons/MicrophoneIcon.d.ts.map +1 -0
  61. package/dist/components/Icons/MicrophoneIcon.figma.d.ts +2 -0
  62. package/dist/components/Icons/MicrophoneIcon.figma.d.ts.map +1 -0
  63. package/dist/components/Icons/NotebookIcon.d.ts +6 -0
  64. package/dist/components/Icons/NotebookIcon.d.ts.map +1 -0
  65. package/dist/components/Icons/NotebookPenIcon.d.ts +6 -0
  66. package/dist/components/Icons/NotebookPenIcon.d.ts.map +1 -0
  67. package/dist/components/Icons/NotebookPenIcon.figma.d.ts +2 -0
  68. package/dist/components/Icons/NotebookPenIcon.figma.d.ts.map +1 -0
  69. package/dist/components/Icons/PausePlayIcon.d.ts +6 -0
  70. package/dist/components/Icons/PausePlayIcon.d.ts.map +1 -0
  71. package/dist/components/Icons/PausePlayIcon.figma.d.ts +2 -0
  72. package/dist/components/Icons/PausePlayIcon.figma.d.ts.map +1 -0
  73. package/dist/components/Icons/PlayIcon.d.ts +6 -0
  74. package/dist/components/Icons/PlayIcon.d.ts.map +1 -0
  75. package/dist/components/Icons/PlayIcon.figma.d.ts +2 -0
  76. package/dist/components/Icons/PlayIcon.figma.d.ts.map +1 -0
  77. package/dist/components/Icons/RecordIcon.d.ts +6 -0
  78. package/dist/components/Icons/RecordIcon.d.ts.map +1 -0
  79. package/dist/components/Icons/RecordIcon.figma.d.ts +2 -0
  80. package/dist/components/Icons/RecordIcon.figma.d.ts.map +1 -0
  81. package/dist/components/Icons/RepeatQuestionIcon.d.ts +6 -0
  82. package/dist/components/Icons/RepeatQuestionIcon.d.ts.map +1 -0
  83. package/dist/components/Icons/RepeatQuestionIcon.figma.d.ts +2 -0
  84. package/dist/components/Icons/RepeatQuestionIcon.figma.d.ts.map +1 -0
  85. package/dist/components/Icons/RightArrowIcon.d.ts +6 -0
  86. package/dist/components/Icons/RightArrowIcon.d.ts.map +1 -0
  87. package/dist/components/Icons/ScenarioIcon.d.ts +6 -0
  88. package/dist/components/Icons/ScenarioIcon.d.ts.map +1 -0
  89. package/dist/components/Icons/ScrollTextIcon.d.ts +6 -0
  90. package/dist/components/Icons/ScrollTextIcon.d.ts.map +1 -0
  91. package/dist/components/Icons/ScrollTextIcon.figma.d.ts +2 -0
  92. package/dist/components/Icons/ScrollTextIcon.figma.d.ts.map +1 -0
  93. package/dist/components/Icons/SparklesIcon.d.ts +6 -0
  94. package/dist/components/Icons/SparklesIcon.d.ts.map +1 -0
  95. package/dist/components/Icons/SparklesIcon.figma.d.ts +2 -0
  96. package/dist/components/Icons/SparklesIcon.figma.d.ts.map +1 -0
  97. package/dist/components/Icons/SpeechIcon.d.ts +6 -0
  98. package/dist/components/Icons/SpeechIcon.d.ts.map +1 -0
  99. package/dist/components/Icons/SpeechIcon.figma.d.ts +2 -0
  100. package/dist/components/Icons/SpeechIcon.figma.d.ts.map +1 -0
  101. package/dist/components/Icons/StopPlayIcon.d.ts +6 -0
  102. package/dist/components/Icons/StopPlayIcon.d.ts.map +1 -0
  103. package/dist/components/Icons/StopPlayIcon.figma.d.ts +2 -0
  104. package/dist/components/Icons/StopPlayIcon.figma.d.ts.map +1 -0
  105. package/dist/components/Icons/TimerIcon.d.ts +6 -0
  106. package/dist/components/Icons/TimerIcon.d.ts.map +1 -0
  107. package/dist/components/Icons/TimerIcon.figma.d.ts +2 -0
  108. package/dist/components/Icons/TimerIcon.figma.d.ts.map +1 -0
  109. package/dist/components/Icons/UserProfileIcon.d.ts +6 -0
  110. package/dist/components/Icons/UserProfileIcon.d.ts.map +1 -0
  111. package/dist/components/Icons/UserProfileIcon.figma.d.ts +2 -0
  112. package/dist/components/Icons/UserProfileIcon.figma.d.ts.map +1 -0
  113. package/dist/components/Icons/index.d.ts +26 -1
  114. package/dist/components/Icons/index.d.ts.map +1 -1
  115. package/dist/components/NavigationMenu/NavigationMenu.d.ts +3 -0
  116. package/dist/components/NavigationMenu/NavigationMenu.d.ts.map +1 -0
  117. package/dist/components/NavigationMenu/NavigationMenu.figma.d.ts +2 -0
  118. package/dist/components/NavigationMenu/NavigationMenu.figma.d.ts.map +1 -0
  119. package/dist/components/NavigationMenu/index.d.ts +3 -0
  120. package/dist/components/NavigationMenu/index.d.ts.map +1 -0
  121. package/dist/components/NavigationMenu/types.d.ts +25 -0
  122. package/dist/components/NavigationMenu/types.d.ts.map +1 -0
  123. package/dist/components/QuickStartPage/QuickStartPage.d.ts +21 -0
  124. package/dist/components/QuickStartPage/QuickStartPage.d.ts.map +1 -0
  125. package/dist/components/QuickStartPage/index.d.ts +3 -0
  126. package/dist/components/QuickStartPage/index.d.ts.map +1 -0
  127. package/dist/components/ScenarioQueue/ScenarioQueue.figma.d.ts +2 -0
  128. package/dist/components/ScenarioQueue/ScenarioQueue.figma.d.ts.map +1 -0
  129. package/dist/components/ScenarioSettings/ScenarioSettings.d.ts +3 -0
  130. package/dist/components/ScenarioSettings/ScenarioSettings.d.ts.map +1 -0
  131. package/dist/components/ScenarioSettings/ScenarioSettings.figma.d.ts +2 -0
  132. package/dist/components/ScenarioSettings/ScenarioSettings.figma.d.ts.map +1 -0
  133. package/dist/components/ScenarioSettings/index.d.ts +3 -0
  134. package/dist/components/ScenarioSettings/index.d.ts.map +1 -0
  135. package/dist/components/ScenarioSettings/types.d.ts +54 -0
  136. package/dist/components/ScenarioSettings/types.d.ts.map +1 -0
  137. package/dist/components/index.cjs +86 -42
  138. package/dist/components/index.d.ts +14 -3
  139. package/dist/components/index.d.ts.map +1 -1
  140. package/dist/components/index.js +1 -1
  141. package/dist/figma-codex/config.d.ts +8 -0
  142. package/dist/figma-codex/config.d.ts.map +1 -0
  143. package/dist/figma-codex/fixtures/CompoundComponent/CompoundComponent.d.ts +6 -0
  144. package/dist/figma-codex/fixtures/CompoundComponent/CompoundComponent.d.ts.map +1 -0
  145. package/dist/figma-codex/fixtures/CompoundComponent/index.d.ts +2 -0
  146. package/dist/figma-codex/fixtures/CompoundComponent/index.d.ts.map +1 -0
  147. package/dist/figma-codex/fixtures/CompoundComponent.figma.d.ts +2 -0
  148. package/dist/figma-codex/fixtures/CompoundComponent.figma.d.ts.map +1 -0
  149. package/dist/figma-codex/fixtures/SimpleComponent.d.ts +8 -0
  150. package/dist/figma-codex/fixtures/SimpleComponent.d.ts.map +1 -0
  151. package/dist/figma-codex/fixtures/SimpleComponent.figma.d.ts +2 -0
  152. package/dist/figma-codex/fixtures/SimpleComponent.figma.d.ts.map +1 -0
  153. package/dist/figma-codex/generate.d.ts +6 -0
  154. package/dist/figma-codex/generate.d.ts.map +1 -0
  155. package/dist/figma-codex/parser.d.ts +18 -0
  156. package/dist/figma-codex/parser.d.ts.map +1 -0
  157. package/dist/figma-codex/resolver.d.ts +5 -0
  158. package/dist/figma-codex/resolver.d.ts.map +1 -0
  159. package/dist/figma-codex/schema.d.ts +60 -0
  160. package/dist/figma-codex/schema.d.ts.map +1 -0
  161. package/dist/figma-codex/writer.d.ts +8 -0
  162. package/dist/figma-codex/writer.d.ts.map +1 -0
  163. package/dist/figma-codex.json +679 -0
  164. package/dist/index.cjs +90 -46
  165. package/dist/index.js +2 -2
  166. package/dist/preset/index.cjs +2 -2
  167. package/dist/preset/index.d.ts.map +1 -1
  168. package/dist/preset/index.js +1 -1
  169. package/dist/preset/recipes/accordion.d.ts.map +1 -1
  170. package/dist/preset/recipes/breadcrumb.d.ts.map +1 -1
  171. package/dist/preset/recipes/content-card.d.ts +2 -0
  172. package/dist/preset/recipes/content-card.d.ts.map +1 -0
  173. package/dist/preset/recipes/index.d.ts +4 -0
  174. package/dist/preset/recipes/index.d.ts.map +1 -1
  175. package/dist/preset/recipes/navigation-menu.d.ts +2 -0
  176. package/dist/preset/recipes/navigation-menu.d.ts.map +1 -0
  177. package/dist/preset/recipes/scenario-settings.d.ts +2 -0
  178. package/dist/preset/recipes/scenario-settings.d.ts.map +1 -0
  179. package/package.json +26 -2
  180. package/src/components/Accordion.figma.tsx +20 -0
  181. package/src/components/Breadcrumb.figma.tsx +18 -0
  182. package/src/components/Breadcrumb.tsx +33 -15
  183. package/src/components/ContentCard/ContentCard.figma.tsx +21 -0
  184. package/src/components/ContentCard/ContentCard.test.tsx +197 -0
  185. package/src/components/ContentCard/ContentCard.tsx +19 -0
  186. package/src/components/ContentCard/index.ts +13 -0
  187. package/src/components/Header.figma.tsx +25 -0
  188. package/src/components/{Heading.tsx → Header.tsx} +2 -2
  189. package/src/components/Icons/AccountIcon.tsx +26 -0
  190. package/src/components/Icons/AudienceIcon.figma.tsx +10 -0
  191. package/src/components/Icons/AudienceIcon.tsx +20 -0
  192. package/src/components/Icons/AudioSpeakerIcon.figma.tsx +10 -0
  193. package/src/components/Icons/AudioSpeakerIcon.tsx +26 -0
  194. package/src/components/Icons/BookmarkPlusIcon.figma.tsx +10 -0
  195. package/src/components/Icons/BookmarkPlusIcon.tsx +26 -0
  196. package/src/components/Icons/ChevronUpIcon.tsx +24 -0
  197. package/src/components/Icons/ClipBoardIcon.figma.tsx +10 -0
  198. package/src/components/Icons/ClipBoardIcon.tsx +61 -0
  199. package/src/components/Icons/ClockIcon.tsx +6 -6
  200. package/src/components/Icons/DashboardIcon.tsx +47 -0
  201. package/src/components/Icons/Discourser-Logo.svg +14 -0
  202. package/src/components/Icons/DiscourserLogo.figma.tsx +10 -0
  203. package/src/components/Icons/DiscourserLogo.tsx +72 -0
  204. package/src/components/Icons/ExitStudioIcon.figma.tsx +10 -0
  205. package/src/components/Icons/ExitStudioIcon.tsx +34 -0
  206. package/src/components/Icons/GripDotsVerticalIcon.tsx +6 -6
  207. package/src/components/Icons/HelpIcon.tsx +26 -0
  208. package/src/components/Icons/MicrophoneIcon.figma.tsx +10 -0
  209. package/src/components/Icons/MicrophoneIcon.tsx +40 -0
  210. package/src/components/Icons/NotebookIcon.tsx +26 -0
  211. package/src/components/Icons/NotebookPenIcon.figma.tsx +10 -0
  212. package/src/components/Icons/NotebookPenIcon.tsx +26 -0
  213. package/src/components/Icons/PausePlayIcon.figma.tsx +10 -0
  214. package/src/components/Icons/PausePlayIcon.tsx +41 -0
  215. package/src/components/Icons/PlayIcon.figma.tsx +10 -0
  216. package/src/components/Icons/PlayIcon.tsx +33 -0
  217. package/src/components/Icons/RecordIcon.figma.tsx +10 -0
  218. package/src/components/Icons/RecordIcon.tsx +41 -0
  219. package/src/components/Icons/RepeatQuestionIcon.figma.tsx +10 -0
  220. package/src/components/Icons/RepeatQuestionIcon.tsx +26 -0
  221. package/src/components/Icons/RightArrowIcon.tsx +23 -0
  222. package/src/components/Icons/ScenarioIcon.tsx +26 -0
  223. package/src/components/Icons/ScrollTextIcon.figma.tsx +10 -0
  224. package/src/components/Icons/ScrollTextIcon.tsx +26 -0
  225. package/src/components/Icons/SparklesIcon.figma.tsx +10 -0
  226. package/src/components/Icons/SparklesIcon.tsx +26 -0
  227. package/src/components/Icons/SpeechIcon.figma.tsx +10 -0
  228. package/src/components/Icons/SpeechIcon.tsx +26 -0
  229. package/src/components/Icons/StopPlayIcon.figma.tsx +10 -0
  230. package/src/components/Icons/StopPlayIcon.tsx +35 -0
  231. package/src/components/Icons/TimerIcon.figma.tsx +10 -0
  232. package/src/components/Icons/TimerIcon.tsx +26 -0
  233. package/src/components/Icons/UserProfileIcon.figma.tsx +10 -0
  234. package/src/components/Icons/UserProfileIcon.tsx +26 -0
  235. package/src/components/Icons/index.ts +39 -2
  236. package/src/components/NavigationMenu/NavigationMenu.figma.tsx +26 -0
  237. package/src/components/NavigationMenu/NavigationMenu.test.tsx +524 -0
  238. package/src/components/NavigationMenu/NavigationMenu.tsx +102 -0
  239. package/src/components/NavigationMenu/index.ts +2 -0
  240. package/src/components/NavigationMenu/types.ts +27 -0
  241. package/src/components/QuickStartPage/QuickStartPage.tsx +627 -0
  242. package/src/components/QuickStartPage/index.ts +2 -0
  243. package/src/components/ScenarioQueue/ScenarioQueue.figma.tsx +37 -0
  244. package/src/components/ScenarioSettings/ScenarioSettings.figma.tsx +12 -0
  245. package/src/components/ScenarioSettings/ScenarioSettings.test.tsx +406 -0
  246. package/src/components/ScenarioSettings/ScenarioSettings.tsx +386 -0
  247. package/src/components/ScenarioSettings/index.ts +11 -0
  248. package/src/components/ScenarioSettings/types.ts +70 -0
  249. package/src/components/__tests__/Breadcrumb.test.tsx +94 -0
  250. package/src/components/index.ts +38 -4
  251. package/src/figma-codex/README.md +186 -0
  252. package/src/figma-codex/__tests__/config.test.ts +63 -0
  253. package/src/figma-codex/__tests__/generate.test.ts +78 -0
  254. package/src/figma-codex/__tests__/parser.test.ts +138 -0
  255. package/src/figma-codex/__tests__/resolver.test.ts +196 -0
  256. package/src/figma-codex/__tests__/writer.test.ts +111 -0
  257. package/src/figma-codex/config.ts +42 -0
  258. package/src/figma-codex/fixtures/CompoundComponent/CompoundComponent.tsx +17 -0
  259. package/src/figma-codex/fixtures/CompoundComponent/index.ts +1 -0
  260. package/src/figma-codex/fixtures/CompoundComponent.figma.tsx +14 -0
  261. package/src/figma-codex/fixtures/SimpleComponent.figma.tsx +10 -0
  262. package/src/figma-codex/fixtures/SimpleComponent.tsx +10 -0
  263. package/src/figma-codex/fixtures/expected-output.json +78 -0
  264. package/src/figma-codex/generate.ts +106 -0
  265. package/src/figma-codex/parser.ts +138 -0
  266. package/src/figma-codex/resolver.ts +280 -0
  267. package/src/figma-codex/schema.ts +79 -0
  268. package/src/figma-codex/writer.ts +54 -0
  269. package/src/preset/index.ts +6 -0
  270. package/src/preset/recipes/accordion.ts +8 -5
  271. package/src/preset/recipes/breadcrumb.ts +34 -2
  272. package/src/preset/recipes/content-card.ts +124 -0
  273. package/src/preset/recipes/index.ts +4 -0
  274. package/src/preset/recipes/navigation-menu.ts +97 -0
  275. package/src/preset/recipes/scenario-settings.ts +182 -0
  276. package/src/test/setup.ts +12 -9
  277. package/dist/chunk-ABC7N32K.cjs.map +0 -1
  278. package/dist/chunk-GD6Q2FUE.js.map +0 -1
  279. package/dist/chunk-SBKRSXSZ.js.map +0 -1
  280. package/dist/chunk-UNWXE6UB.cjs.map +0 -1
  281. package/dist/components/Heading.d.ts.map +0 -1
@@ -0,0 +1,186 @@
1
+ # figma-codex
2
+
3
+ A portable manifest generator that reads `.figma.tsx` Code Connect files and produces `figma-codex.json` — a structured map of Figma design components to their code implementations.
4
+
5
+ ## Why It Exists
6
+
7
+ Figma's Code Connect "inspect" feature (linking designs to code in Dev Mode) requires a Figma **Organization or Enterprise** plan. `figma-codex` achieves the same goal without the subscription: it reads the `.figma.tsx` connector files already in the codebase and produces a self-contained JSON manifest that AI agents can consume offline.
8
+
9
+ ## What It Produces
10
+
11
+ `dist/figma-codex.json` contains:
12
+
13
+ ```json
14
+ {
15
+ "version": "1.0.0",
16
+ "packageName": "@discourser/design-system",
17
+ "generatedAt": "2026-02-27T12:00:00.000Z",
18
+ "gitHash": "abc1234",
19
+ "figmaFiles": {
20
+ "GaHmFfmvO4loUzuZS4TgEz": { "fileKey": "GaHmFfmvO4loUzuZS4TgEz" }
21
+ },
22
+ "components": {
23
+ "Accordion": {
24
+ "name": "Accordion",
25
+ "type": "compound",
26
+ "figma": {
27
+ "fileKey": "GaHmFfmvO4loUzuZS4TgEz",
28
+ "nodeId": "38:7978",
29
+ "url": "https://www.figma.com/design/..."
30
+ },
31
+ "imports": {
32
+ "primary": "import * as Accordion from '@discourser/design-system/Accordion'",
33
+ "namedExports": ["Accordion.Root", "Accordion.Item"]
34
+ },
35
+ "props": [],
36
+ "subComponents": [
37
+ { "name": "Root", "element": "div" },
38
+ { "name": "Item", "element": "div" }
39
+ ],
40
+ "example": "<Accordion.Root>...</Accordion.Root>",
41
+ "sourcePath": "src/components/Accordion.tsx"
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Component Types
48
+
49
+ | Type | Description | Example |
50
+ | ----------- | ------------------------------------------------------------- | --------------------------------- |
51
+ | `simple` | Single named export, no compound structure | `Header`, `DiscourserLogo` |
52
+ | `compound` | Namespace import with sub-components (`.Root`, `.Item`, etc.) | `Accordion`, `Breadcrumb` |
53
+ | `composite` | Named export that imports other DDS components internally | `NavigationMenu`, `ScenarioQueue` |
54
+
55
+ ## Usage
56
+
57
+ ### Generate the manifest
58
+
59
+ ```bash
60
+ # One-time generation
61
+ pnpm codex:generate
62
+
63
+ # Watch mode (re-generates when .figma.tsx files change)
64
+ pnpm codex:watch
65
+ ```
66
+
67
+ The manifest is also auto-generated at the end of `pnpm build`.
68
+
69
+ ### Auto-regeneration on commit
70
+
71
+ When you commit a `.figma.tsx` file, lint-staged automatically runs the generator (`tsx src/figma-codex/generate.ts`) to keep the manifest in sync.
72
+
73
+ ### Consuming the manifest
74
+
75
+ ```typescript
76
+ // In AI agent code or tooling scripts
77
+ import codex from '@discourser/design-system/figma-codex';
78
+
79
+ const accordion = codex.components['Accordion'];
80
+ console.log(accordion.type); // 'compound'
81
+ console.log(accordion.imports.primary); // "import * as Accordion from '@discourser/design-system/Accordion'"
82
+ console.log(accordion.subComponents); // [{ name: 'Root', element: 'div' }, ...]
83
+ ```
84
+
85
+ ## Agent Workflow (Kai + Amelia)
86
+
87
+ 1. **Kai** (Design Engineer) loads `figma-codex.json` and uses `figma.nodeId` to look up the Figma component
88
+ 2. Kai reads `imports.primary`, `props`, and `example` to understand how to use the component in code
89
+ 3. **Amelia** (Developer Agent) uses the manifest to verify the correct import path and prop types when implementing stories
90
+ 4. Both agents benefit from `subComponents` entries when assembling compound components
91
+
92
+ ## Adding a New Component
93
+
94
+ After creating `YourComponent.figma.tsx`, three import patterns are supported:
95
+
96
+ **Pattern 1 — Named import (simple)**
97
+
98
+ ```tsx
99
+ import { YourComponent } from './YourComponent';
100
+ figma.connect(YourComponent, 'https://www.figma.com/design/...', {
101
+ example: () => <YourComponent />,
102
+ });
103
+ ```
104
+
105
+ **Pattern 2 — Namespace from component file (compound)**
106
+
107
+ ```tsx
108
+ import * as YourComponent from './YourComponent';
109
+ figma.connect(YourComponent.Root, 'https://www.figma.com/design/...', {
110
+ example: () => <YourComponent.Root />,
111
+ });
112
+ ```
113
+
114
+ **Pattern 3 — Namespace from index (compound)**
115
+
116
+ ```tsx
117
+ import * as YourComponent from './YourComponent/index';
118
+ figma.connect(YourComponent.Root, 'https://www.figma.com/design/...', {
119
+ example: () => <YourComponent.Root />,
120
+ });
121
+ ```
122
+
123
+ After creating the file, run `pnpm codex:generate` to update the manifest.
124
+
125
+ ## Porting to Another Design System
126
+
127
+ 1. Copy the `src/figma-codex/` directory to your repo
128
+ 2. Add `"codex:generate": "tsx src/figma-codex/generate.ts"` to `package.json` scripts
129
+ 3. Create `figma-codex.config.json` (optional, all fields have defaults):
130
+
131
+ ```json
132
+ {
133
+ "include": ["src/components/**/*.figma.tsx"],
134
+ "outputPath": "dist/figma-codex.json",
135
+ "packageName": "@your-org/design-system"
136
+ }
137
+ ```
138
+
139
+ 4. Run `pnpm codex:generate`
140
+
141
+ ## Schema Reference
142
+
143
+ ```typescript
144
+ interface FigmaCodex {
145
+ version: '1.0.0';
146
+ packageName: string;
147
+ generatedAt: string; // ISO 8601
148
+ gitHash?: string; // short SHA
149
+ figmaFiles: Record<string, { fileKey: string; fileName?: string }>;
150
+ components: Record<string, ComponentEntry>;
151
+ }
152
+
153
+ interface ComponentEntry {
154
+ name: string;
155
+ type: 'simple' | 'compound' | 'composite';
156
+ figma: {
157
+ fileKey: string;
158
+ nodeId: string; // format: "123:456"
159
+ nodeName?: string;
160
+ url: string;
161
+ };
162
+ imports: {
163
+ primary: string; // package import path
164
+ namedExports: string[];
165
+ subpath?: string;
166
+ };
167
+ props: PropDefinition[];
168
+ subComponents?: SubComponentEntry[];
169
+ example: string; // JSX usage string from figma.connect()
170
+ sourcePath: string; // relative path to source .tsx file
171
+ }
172
+
173
+ interface PropDefinition {
174
+ name: string;
175
+ type: string;
176
+ required: boolean;
177
+ description?: string; // from JSDoc comment
178
+ defaultValue?: string;
179
+ }
180
+
181
+ interface SubComponentEntry {
182
+ name: string; // e.g. "Root", "Item", "Header"
183
+ element: string; // underlying ark element, e.g. "div"
184
+ description?: string;
185
+ }
186
+ ```
@@ -0,0 +1,63 @@
1
+ // @vitest-environment node
2
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
3
+ import { writeFileSync, rmSync, mkdirSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { loadConfig } from '../config';
7
+
8
+ describe('loadConfig', () => {
9
+ let tmpDir: string;
10
+
11
+ beforeEach(() => {
12
+ tmpDir = join(tmpdir(), `figma-codex-test-${Date.now()}`);
13
+ mkdirSync(tmpDir, { recursive: true });
14
+ });
15
+
16
+ afterEach(() => {
17
+ rmSync(tmpDir, { recursive: true, force: true });
18
+ });
19
+
20
+ it('returns defaults when no config file exists', () => {
21
+ const config = loadConfig(tmpDir);
22
+ expect(config.include).toEqual(['src/components/**/*.figma.tsx']);
23
+ expect(config.outputPath).toBe('dist/figma-codex.json');
24
+ expect(config.tsconfig).toBe('tsconfig.json');
25
+ });
26
+
27
+ it('merges user config over defaults', () => {
28
+ const userConfig = {
29
+ include: ['src/custom/**/*.figma.tsx'],
30
+ outputPath: 'out/codex.json',
31
+ };
32
+ writeFileSync(
33
+ join(tmpDir, 'figma-codex.config.json'),
34
+ JSON.stringify(userConfig),
35
+ );
36
+ const config = loadConfig(tmpDir);
37
+ expect(config.include).toEqual(['src/custom/**/*.figma.tsx']);
38
+ expect(config.outputPath).toBe('out/codex.json');
39
+ expect(config.tsconfig).toBe('tsconfig.json'); // default preserved
40
+ });
41
+
42
+ it('reads packageName from package.json when not set in config', () => {
43
+ writeFileSync(
44
+ join(tmpDir, 'package.json'),
45
+ JSON.stringify({ name: '@my/design-system' }),
46
+ );
47
+ const config = loadConfig(tmpDir);
48
+ expect(config.packageName).toBe('@my/design-system');
49
+ });
50
+
51
+ it('prefers packageName from config over package.json', () => {
52
+ writeFileSync(
53
+ join(tmpDir, 'package.json'),
54
+ JSON.stringify({ name: '@my/design-system' }),
55
+ );
56
+ writeFileSync(
57
+ join(tmpDir, 'figma-codex.config.json'),
58
+ JSON.stringify({ packageName: 'override-name' }),
59
+ );
60
+ const config = loadConfig(tmpDir);
61
+ expect(config.packageName).toBe('override-name');
62
+ });
63
+ });
@@ -0,0 +1,78 @@
1
+ // @vitest-environment node
2
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
3
+ import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { generate } from '../generate';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ const FIXTURES = join(__dirname, '../fixtures');
13
+
14
+ describe('generate (integration)', () => {
15
+ let tmpOut: string;
16
+ let outputPath: string;
17
+ let manifest: Record<string, unknown>;
18
+
19
+ beforeAll(() => {
20
+ tmpOut = join(tmpdir(), `figma-codex-integration-${Date.now()}`);
21
+ mkdirSync(tmpOut, { recursive: true });
22
+ outputPath = join(tmpOut, 'figma-codex.json');
23
+
24
+ generate({
25
+ include: [`${FIXTURES}/*.figma.tsx`],
26
+ outputPath,
27
+ packageName: '@test/design-system',
28
+ tsconfig: 'tsconfig.json',
29
+ projectRoot: FIXTURES,
30
+ });
31
+
32
+ manifest = JSON.parse(readFileSync(outputPath, 'utf-8')) as Record<
33
+ string,
34
+ unknown
35
+ >;
36
+ });
37
+
38
+ afterAll(() => {
39
+ rmSync(tmpOut, { recursive: true, force: true });
40
+ });
41
+
42
+ it('creates the output file', () => {
43
+ expect(existsSync(outputPath)).toBe(true);
44
+ });
45
+
46
+ it('includes SimpleComponent', () => {
47
+ const components = manifest.components as Record<string, unknown>;
48
+ expect(components).toHaveProperty('SimpleComponent');
49
+ });
50
+
51
+ it('includes CompoundComponent', () => {
52
+ const components = manifest.components as Record<string, unknown>;
53
+ expect(components).toHaveProperty('CompoundComponent');
54
+ });
55
+
56
+ it('SimpleComponent is type simple', () => {
57
+ const components = manifest.components as Record<string, { type: string }>;
58
+ expect(components.SimpleComponent.type).toBe('simple');
59
+ });
60
+
61
+ it('CompoundComponent is type compound', () => {
62
+ const components = manifest.components as Record<string, { type: string }>;
63
+ expect(components.CompoundComponent.type).toBe('compound');
64
+ });
65
+
66
+ it('has valid schema version', () => {
67
+ expect(manifest.version).toBe('1.0.0');
68
+ });
69
+
70
+ it('has generatedAt timestamp', () => {
71
+ expect(typeof manifest.generatedAt).toBe('string');
72
+ });
73
+
74
+ it('has non-empty components map', () => {
75
+ const components = manifest.components as Record<string, unknown>;
76
+ expect(Object.keys(components).length).toBeGreaterThan(0);
77
+ });
78
+ });
@@ -0,0 +1,138 @@
1
+ // @vitest-environment node
2
+ import { describe, it, expect, beforeEach } from 'vitest';
3
+ import { readFileSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { parseFigmaFile } from '../parser';
7
+ import type { ParsedFigmaFile } from '../parser';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ const FIXTURES = join(__dirname, '../fixtures');
13
+
14
+ describe('parseFigmaFile', () => {
15
+ describe('named import (simple/composite pattern)', () => {
16
+ let result: ParsedFigmaFile;
17
+
18
+ beforeEach(() => {
19
+ const content = readFileSync(
20
+ join(FIXTURES, 'SimpleComponent.figma.tsx'),
21
+ 'utf-8',
22
+ );
23
+ result = parseFigmaFile(
24
+ content,
25
+ join(FIXTURES, 'SimpleComponent.figma.tsx'),
26
+ );
27
+ });
28
+
29
+ it('detects named import style', () => {
30
+ expect(result.importStyle).toBe('named');
31
+ });
32
+
33
+ it('extracts component name', () => {
34
+ expect(result.componentName).toBe('SimpleComponent');
35
+ });
36
+
37
+ it('extracts import source path', () => {
38
+ expect(result.importSource).toBe('./SimpleComponent');
39
+ });
40
+
41
+ it('extracts figma URL', () => {
42
+ expect(result.figmaUrl).toBe(
43
+ 'https://www.figma.com/design/ABC123/TestFile?node-id=1-2',
44
+ );
45
+ });
46
+
47
+ it('extracts file key from URL', () => {
48
+ expect(result.figmaFileKey).toBe('ABC123');
49
+ });
50
+
51
+ it('extracts node ID from URL (converting dash to colon)', () => {
52
+ expect(result.figmaNodeId).toBe('1:2');
53
+ });
54
+
55
+ it('extracts example JSX as string', () => {
56
+ expect(result.example).toContain('SimpleComponent');
57
+ expect(result.example).toContain('label="Hello"');
58
+ });
59
+
60
+ it('returns empty props mapping when no props block', () => {
61
+ expect(result.propsMapping).toEqual({});
62
+ });
63
+ });
64
+
65
+ describe('namespace import (compound pattern)', () => {
66
+ let result: ParsedFigmaFile;
67
+
68
+ beforeEach(() => {
69
+ const content = readFileSync(
70
+ join(FIXTURES, 'CompoundComponent.figma.tsx'),
71
+ 'utf-8',
72
+ );
73
+ result = parseFigmaFile(
74
+ content,
75
+ join(FIXTURES, 'CompoundComponent.figma.tsx'),
76
+ );
77
+ });
78
+
79
+ it('detects namespace import style', () => {
80
+ expect(result.importStyle).toBe('namespace');
81
+ });
82
+
83
+ it('extracts namespace identifier', () => {
84
+ expect(result.componentName).toBe('CompoundComponent');
85
+ });
86
+
87
+ it('extracts import source', () => {
88
+ expect(result.importSource).toBe('./CompoundComponent/index');
89
+ });
90
+
91
+ it('extracts sub-component name from connect call (e.g., Root)', () => {
92
+ expect(result.connectSubComponent).toBe('Root');
93
+ });
94
+
95
+ it('extracts node ID correctly', () => {
96
+ expect(result.figmaNodeId).toBe('5:10');
97
+ });
98
+
99
+ it('extracts multiline example JSX', () => {
100
+ expect(result.example).toContain('CompoundComponent.Root');
101
+ expect(result.example).toContain('CompoundComponent.Header');
102
+ });
103
+ });
104
+
105
+ describe('props mapping extraction', () => {
106
+ it('extracts prop names from figma.enum props block', () => {
107
+ const content = `
108
+ import figma from '@figma/code-connect'
109
+ import { Header } from './Header'
110
+ figma.connect(Header, 'https://www.figma.com/design/GaHmFfmvO4loUzuZS4TgEz/File?node-id=485-4697', {
111
+ props: {
112
+ size: figma.enum('size', { xSm: 'xs', Mdm: 'md' }),
113
+ },
114
+ example: ({ size }) => <Header size={size}>Text</Header>,
115
+ })
116
+ `.trim();
117
+ const result = parseFigmaFile(content, '/fake/Header.figma.tsx');
118
+ expect(result.propsMapping).toHaveProperty('size');
119
+ });
120
+ });
121
+
122
+ describe('error handling', () => {
123
+ it('throws ParseError for file with no figma.connect call', () => {
124
+ expect(() =>
125
+ parseFigmaFile('import React from "react"', '/fake/empty.figma.tsx'),
126
+ ).toThrow();
127
+ });
128
+
129
+ it('throws ParseError for file with no import', () => {
130
+ expect(() =>
131
+ parseFigmaFile(
132
+ `figma.connect(X, 'https://figma.com/design/A/F?node-id=1-2', { example: () => <X /> })`,
133
+ '/fake/noimport.figma.tsx',
134
+ ),
135
+ ).toThrow();
136
+ });
137
+ });
138
+ });
@@ -0,0 +1,196 @@
1
+ // @vitest-environment node
2
+ import { describe, it, expect } from 'vitest';
3
+ import { join, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { resolveComponent } from '../resolver';
6
+ import type { ParsedFigmaFile } from '../parser';
7
+ import type { FigmaCodexConfig } from '../config';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ const FIXTURES = join(__dirname, '../fixtures');
13
+ const DDS_ROOT = join(__dirname, '../../..');
14
+
15
+ const baseConfig: FigmaCodexConfig = {
16
+ include: [],
17
+ outputPath: 'dist/figma-codex.json',
18
+ packageName: '@discourser/design-system',
19
+ tsconfig: 'tsconfig.json',
20
+ };
21
+
22
+ describe('resolveComponent', () => {
23
+ describe('simple component', () => {
24
+ const parsed: ParsedFigmaFile = {
25
+ filePath: join(FIXTURES, 'SimpleComponent.figma.tsx'),
26
+ importStyle: 'named',
27
+ componentName: 'SimpleComponent',
28
+ importSource: './SimpleComponent',
29
+ figmaUrl: 'https://www.figma.com/design/ABC123/TestFile?node-id=1-2',
30
+ figmaFileKey: 'ABC123',
31
+ figmaNodeId: '1:2',
32
+ example: '<SimpleComponent label="Hello" />',
33
+ propsMapping: {},
34
+ };
35
+
36
+ it('classifies as simple', () => {
37
+ const entry = resolveComponent(parsed, baseConfig);
38
+ expect(entry.type).toBe('simple');
39
+ });
40
+
41
+ it('extracts named exports from source', () => {
42
+ const entry = resolveComponent(parsed, baseConfig);
43
+ expect(entry.imports.namedExports).toContain('SimpleComponent');
44
+ });
45
+
46
+ it('extracts prop definitions', () => {
47
+ const entry = resolveComponent(parsed, baseConfig);
48
+ expect(entry.props.length).toBeGreaterThan(0);
49
+ const labelProp = entry.props.find((p) => p.name === 'label');
50
+ expect(labelProp).toBeDefined();
51
+ expect(labelProp?.required).toBe(true);
52
+ });
53
+
54
+ it('extracts optional props', () => {
55
+ const entry = resolveComponent(parsed, baseConfig);
56
+ const sizeProp = entry.props.find((p) => p.name === 'size');
57
+ expect(sizeProp).toBeDefined();
58
+ expect(sizeProp?.required).toBe(false);
59
+ });
60
+
61
+ it('builds correct figma metadata', () => {
62
+ const entry = resolveComponent(parsed, baseConfig);
63
+ expect(entry.figma.fileKey).toBe('ABC123');
64
+ expect(entry.figma.nodeId).toBe('1:2');
65
+ expect(entry.figma.url).toBe(parsed.figmaUrl);
66
+ });
67
+
68
+ it('builds primary import statement', () => {
69
+ const entry = resolveComponent(parsed, baseConfig);
70
+ expect(entry.imports.primary).toContain('SimpleComponent');
71
+ expect(entry.imports.primary).toContain('@discourser/design-system');
72
+ });
73
+ });
74
+
75
+ describe('compound component', () => {
76
+ const parsed: ParsedFigmaFile = {
77
+ filePath: join(FIXTURES, 'CompoundComponent.figma.tsx'),
78
+ importStyle: 'namespace',
79
+ componentName: 'CompoundComponent',
80
+ importSource: './CompoundComponent/index',
81
+ connectSubComponent: 'Root',
82
+ figmaUrl: 'https://www.figma.com/design/ABC123/TestFile?node-id=5-10',
83
+ figmaFileKey: 'ABC123',
84
+ figmaNodeId: '5:10',
85
+ example: '<CompoundComponent.Root>...</CompoundComponent.Root>',
86
+ propsMapping: {},
87
+ };
88
+
89
+ it('classifies as compound', () => {
90
+ const entry = resolveComponent(parsed, baseConfig);
91
+ expect(entry.type).toBe('compound');
92
+ });
93
+
94
+ it('extracts sub-components from withContext/withProvider calls', () => {
95
+ const entry = resolveComponent(parsed, baseConfig);
96
+ expect(entry.subComponents).toBeDefined();
97
+ const names = entry.subComponents?.map((s) => s.name) ?? [];
98
+ expect(names).toContain('Root');
99
+ expect(names).toContain('Header');
100
+ expect(names).toContain('Body');
101
+ });
102
+
103
+ it('extracts HTML element for sub-components', () => {
104
+ const entry = resolveComponent(parsed, baseConfig);
105
+ const rootSub = entry.subComponents?.find((s) => s.name === 'Root');
106
+ expect(rootSub?.element).toBeTruthy();
107
+ });
108
+
109
+ it('builds namespace import statement', () => {
110
+ const entry = resolveComponent(parsed, baseConfig);
111
+ expect(entry.imports.primary).toContain('* as CompoundComponent');
112
+ });
113
+ });
114
+
115
+ describe('composite component detection', () => {
116
+ it('classifies NavigationMenu as composite (imports other DDS components)', () => {
117
+ const parsed: ParsedFigmaFile = {
118
+ filePath: join(
119
+ DDS_ROOT,
120
+ 'src/components/NavigationMenu/NavigationMenu.figma.tsx',
121
+ ),
122
+ importStyle: 'named',
123
+ componentName: 'NavigationMenu',
124
+ importSource: './NavigationMenu',
125
+ figmaUrl:
126
+ 'https://www.figma.com/design/GaHmFfmvO4loUzuZS4TgEz/Discourser.AI--V1?node-id=38-8485',
127
+ figmaFileKey: 'GaHmFfmvO4loUzuZS4TgEz',
128
+ figmaNodeId: '38:8485',
129
+ example: '<NavigationMenu sections={[]} />',
130
+ propsMapping: {},
131
+ };
132
+ const entry = resolveComponent(parsed, {
133
+ ...baseConfig,
134
+ packageName: '@discourser/design-system',
135
+ });
136
+ expect(entry.type).toBe('composite');
137
+ });
138
+ });
139
+
140
+ describe('missing source file', () => {
141
+ it('returns empty props array when source file not found', () => {
142
+ const parsed: ParsedFigmaFile = {
143
+ filePath: '/nonexistent/dir/Missing.figma.tsx',
144
+ importStyle: 'named',
145
+ componentName: 'Missing',
146
+ importSource: './Missing',
147
+ figmaUrl: 'https://www.figma.com/design/X/F?node-id=1-1',
148
+ figmaFileKey: 'X',
149
+ figmaNodeId: '1:1',
150
+ example: '<Missing />',
151
+ propsMapping: {},
152
+ };
153
+ const entry = resolveComponent(parsed, baseConfig);
154
+ expect(entry.props).toEqual([]);
155
+ });
156
+ });
157
+
158
+ describe('NavigationMenu multi-line prop types', () => {
159
+ const parsed: ParsedFigmaFile = {
160
+ filePath: join(
161
+ DDS_ROOT,
162
+ 'src/components/NavigationMenu/NavigationMenu.figma.tsx',
163
+ ),
164
+ importStyle: 'named',
165
+ componentName: 'NavigationMenu',
166
+ importSource: './NavigationMenu',
167
+ figmaUrl:
168
+ 'https://www.figma.com/design/GaHmFfmvO4loUzuZS4TgEz/Discourser.AI--V1?node-id=38-8485',
169
+ figmaFileKey: 'GaHmFfmvO4loUzuZS4TgEz',
170
+ figmaNodeId: '38:8485',
171
+ example: '<NavigationMenu sections={[]} />',
172
+ propsMapping: {},
173
+ };
174
+
175
+ it('extracts exactly 6 props (not leaking inner fields)', () => {
176
+ const entry = resolveComponent(parsed, baseConfig);
177
+ expect(entry.props).toHaveLength(6);
178
+ });
179
+
180
+ it('renderLink type contains full function signature', () => {
181
+ const entry = resolveComponent(parsed, baseConfig);
182
+ const renderLink = entry.props.find((p) => p.name === 'renderLink');
183
+ expect(renderLink).toBeDefined();
184
+ expect(renderLink?.type).toContain('=> React.ReactNode');
185
+ expect(renderLink?.type).toContain('href');
186
+ });
187
+
188
+ it('does not leak inner object fields as top-level props', () => {
189
+ const entry = resolveComponent(parsed, baseConfig);
190
+ const names = entry.props.map((p) => p.name);
191
+ expect(names).not.toContain('href');
192
+ expect(names).not.toContain('isActive');
193
+ expect(names).not.toContain('className');
194
+ });
195
+ });
196
+ });