@recursica/adapter-mantine-v8 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 (362) hide show
  1. package/.storybook/commonArgTypes.ts +148 -0
  2. package/ARCHITECTURE.md +18 -0
  3. package/CHANGELOG.md +688 -0
  4. package/LICENSE +21 -0
  5. package/OVERSTYLING.md +56 -0
  6. package/README.md +70 -0
  7. package/SETUP.md +93 -0
  8. package/USAGE.md +57 -0
  9. package/dist/adapter-mantine-v8.cjs +12 -0
  10. package/dist/adapter-mantine-v8.cjs.map +1 -0
  11. package/dist/adapter-mantine-v8.css +1 -0
  12. package/dist/adapter-mantine-v8.js +5759 -0
  13. package/dist/adapter-mantine-v8.js.map +1 -0
  14. package/dist/index.d.ts +2095 -0
  15. package/dist/test.html +1 -0
  16. package/dist/vite.svg +1 -0
  17. package/docs/PHILOSOPHY.md +39 -0
  18. package/llms.txt +74 -0
  19. package/package.json +131 -0
  20. package/recursica_brand.json +2677 -0
  21. package/recursica_tokens.json +799 -0
  22. package/recursica_ui-kit.json +14981 -0
  23. package/recursica_variables_scoped.css +30489 -0
  24. package/src/GlobalExemptions.modules.css +7 -0
  25. package/src/Introduction.stories.tsx +154 -0
  26. package/src/OverStyling.tsx +79 -0
  27. package/src/Version.tsx +44 -0
  28. package/src/components/Accordion/ACCORDION_IMPLEMENTATION_NOTES.md +72 -0
  29. package/src/components/Accordion/Accordion.module.css +367 -0
  30. package/src/components/Accordion/Accordion.stories.tsx +189 -0
  31. package/src/components/Accordion/Accordion.test.tsx +1 -0
  32. package/src/components/Accordion/Accordion.tsx +220 -0
  33. package/src/components/Accordion/USAGE.md +51 -0
  34. package/src/components/Accordion/index.ts +1 -0
  35. package/src/components/AssistiveElement/ASSISTIVEELEMENT_IMPLEMENTATION_NOTES.md +45 -0
  36. package/src/components/AssistiveElement/AssistiveElement.module.css +111 -0
  37. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +68 -0
  38. package/src/components/AssistiveElement/AssistiveElement.test.tsx +1 -0
  39. package/src/components/AssistiveElement/AssistiveElement.tsx +93 -0
  40. package/src/components/AssistiveElement/USAGE.md +36 -0
  41. package/src/components/AutoComplete/AUTOCOMPLETE_IMPLEMENTATION_NOTES.md +42 -0
  42. package/src/components/AutoComplete/AutoComplete.module.css +430 -0
  43. package/src/components/AutoComplete/AutoComplete.stories.tsx +336 -0
  44. package/src/components/AutoComplete/AutoComplete.test.tsx +1 -0
  45. package/src/components/AutoComplete/AutoComplete.tsx +179 -0
  46. package/src/components/AutoComplete/USAGE.md +80 -0
  47. package/src/components/Avatar/AVATAR_IMPLEMENTATION_NOTES.md +34 -0
  48. package/src/components/Avatar/Avatar.module.css +336 -0
  49. package/src/components/Avatar/Avatar.stories.tsx +92 -0
  50. package/src/components/Avatar/Avatar.test.tsx +1 -0
  51. package/src/components/Avatar/Avatar.tsx +108 -0
  52. package/src/components/Avatar/USAGE.md +38 -0
  53. package/src/components/Avatar/index.ts +1 -0
  54. package/src/components/Badge/BADGE_IMPLEMENTATION_NOTES.md +18 -0
  55. package/src/components/Badge/Badge.module.css +127 -0
  56. package/src/components/Badge/Badge.stories.tsx +78 -0
  57. package/src/components/Badge/Badge.test.tsx +1 -0
  58. package/src/components/Badge/Badge.tsx +64 -0
  59. package/src/components/Badge/USAGE.md +56 -0
  60. package/src/components/Breadcrumb/BREADCRUMB_IMPLEMENTATION_NOTES.md +47 -0
  61. package/src/components/Breadcrumb/Breadcrumb.module.css +48 -0
  62. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
  63. package/src/components/Breadcrumb/Breadcrumb.test.tsx +1 -0
  64. package/src/components/Breadcrumb/Breadcrumb.tsx +85 -0
  65. package/src/components/Breadcrumb/USAGE.md +55 -0
  66. package/src/components/Breadcrumb/markCurrentPageItem.test.ts +49 -0
  67. package/src/components/Button/Button.module.css +446 -0
  68. package/src/components/Button/Button.stories.tsx +196 -0
  69. package/src/components/Button/Button.styleIsolation.dom.test.tsx +98 -0
  70. package/src/components/Button/Button.tsx +184 -0
  71. package/src/components/Button/IMPLEMENTATION_NOTES.md +109 -0
  72. package/src/components/Button/USAGE.md +80 -0
  73. package/src/components/Button/index.ts +1 -0
  74. package/src/components/Card/CARD_IMPLEMENTATION_NOTES.md +17 -0
  75. package/src/components/Card/Card.module.css +119 -0
  76. package/src/components/Card/Card.stories.tsx +131 -0
  77. package/src/components/Card/Card.test.tsx +1 -0
  78. package/src/components/Card/Card.tsx +248 -0
  79. package/src/components/Card/USAGE.md +63 -0
  80. package/src/components/Checkbox/CHECKBOX_IMPLEMENTATION_NOTES.md +25 -0
  81. package/src/components/Checkbox/Checkbox.module.css +267 -0
  82. package/src/components/Checkbox/Checkbox.stories.tsx +97 -0
  83. package/src/components/Checkbox/Checkbox.test.tsx +1 -0
  84. package/src/components/Checkbox/Checkbox.tsx +151 -0
  85. package/src/components/Checkbox/CheckboxGroup.stories.tsx +139 -0
  86. package/src/components/Checkbox/CheckboxGroup.tsx +147 -0
  87. package/src/components/Checkbox/USAGE.md +48 -0
  88. package/src/components/Chip/CHIP_IMPLEMENTATION_NOTES.md +110 -0
  89. package/src/components/Chip/Chip.module.css +288 -0
  90. package/src/components/Chip/Chip.stories.tsx +128 -0
  91. package/src/components/Chip/Chip.test.tsx +1 -0
  92. package/src/components/Chip/Chip.tsx +157 -0
  93. package/src/components/Chip/USAGE.md +55 -0
  94. package/src/components/Container/CONTAINER_IMPLEMENTATION_NOTES.md +4 -0
  95. package/src/components/Container/Container.module.css +7 -0
  96. package/src/components/Container/Container.stories.tsx +118 -0
  97. package/src/components/Container/Container.test.tsx +1 -0
  98. package/src/components/Container/Container.tsx +69 -0
  99. package/src/components/Container/USAGE.md +47 -0
  100. package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +31 -0
  101. package/src/components/DatePicker/DatePicker.icons.tsx +27 -0
  102. package/src/components/DatePicker/DatePicker.module.css +566 -0
  103. package/src/components/DatePicker/DatePicker.stories.tsx +158 -0
  104. package/src/components/DatePicker/DatePicker.test.tsx +1 -0
  105. package/src/components/DatePicker/DatePicker.tsx +218 -0
  106. package/src/components/DatePicker/USAGE.md +49 -0
  107. package/src/components/Dropdown/BareDropdown.tsx +97 -0
  108. package/src/components/Dropdown/DROPDOWN_IMPLEMENTATION_NOTES.md +12 -0
  109. package/src/components/Dropdown/Dropdown.module.css +477 -0
  110. package/src/components/Dropdown/Dropdown.stories.tsx +252 -0
  111. package/src/components/Dropdown/Dropdown.test.tsx +1 -0
  112. package/src/components/Dropdown/Dropdown.tsx +213 -0
  113. package/src/components/Dropdown/USAGE.md +69 -0
  114. package/src/components/FileInput/FILEINPUT_IMPLEMENTATION_NOTES.md +153 -0
  115. package/src/components/FileInput/FileInput.module.css +280 -0
  116. package/src/components/FileInput/FileInput.stories.tsx +313 -0
  117. package/src/components/FileInput/FileInput.test.tsx +1 -0
  118. package/src/components/FileInput/FileInput.tsx +417 -0
  119. package/src/components/FileInput/USAGE.md +145 -0
  120. package/src/components/FileUpload/FILEUPLOAD_IMPLEMENTATION_NOTES.md +244 -0
  121. package/src/components/FileUpload/FileUpload.module.css +205 -0
  122. package/src/components/FileUpload/FileUpload.stories.tsx +362 -0
  123. package/src/components/FileUpload/FileUpload.test.tsx +1 -0
  124. package/src/components/FileUpload/FileUpload.tsx +354 -0
  125. package/src/components/FileUpload/USAGE.md +199 -0
  126. package/src/components/Flex/FLEX_IMPLEMENTATION_NOTES.md +4 -0
  127. package/src/components/Flex/Flex.module.css +7 -0
  128. package/src/components/Flex/Flex.stories.tsx +128 -0
  129. package/src/components/Flex/Flex.test.tsx +1 -0
  130. package/src/components/Flex/Flex.tsx +75 -0
  131. package/src/components/Flex/USAGE.md +48 -0
  132. package/src/components/FormControlLayout/FormControlLayout.module.css +90 -0
  133. package/src/components/FormControlLayout/FormControlLayout.stories.tsx +118 -0
  134. package/src/components/FormControlLayout/FormControlLayout.test.tsx +1 -0
  135. package/src/components/FormControlLayout/FormControlLayout.tsx +74 -0
  136. package/src/components/FormControlLayout/USAGE.md +40 -0
  137. package/src/components/FormControlWrapper/FORMCONTROLWRAPPER_IMPLEMENTATION_NOTES.md +32 -0
  138. package/src/components/FormControlWrapper/FormControlWrapper.module.css +75 -0
  139. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +129 -0
  140. package/src/components/FormControlWrapper/FormControlWrapper.test.tsx +1 -0
  141. package/src/components/FormControlWrapper/FormControlWrapper.tsx +151 -0
  142. package/src/components/FormControlWrapper/USAGE.md +47 -0
  143. package/src/components/Grid/GRID_IMPLEMENTATION_NOTES.md +18 -0
  144. package/src/components/Grid/Grid.module.css +11 -0
  145. package/src/components/Grid/Grid.stories.tsx +206 -0
  146. package/src/components/Grid/Grid.test.tsx +1 -0
  147. package/src/components/Grid/Grid.tsx +140 -0
  148. package/src/components/Grid/USAGE.md +47 -0
  149. package/src/components/Group/GROUP_IMPLEMENTATION_NOTES.md +4 -0
  150. package/src/components/Group/Group.module.css +7 -0
  151. package/src/components/Group/Group.stories.tsx +120 -0
  152. package/src/components/Group/Group.test.tsx +1 -0
  153. package/src/components/Group/Group.tsx +60 -0
  154. package/src/components/Group/USAGE.md +48 -0
  155. package/src/components/Heading/HEADING_IMPLEMENTATION_NOTES.md +18 -0
  156. package/src/components/Heading/Heading.module.css +13 -0
  157. package/src/components/Heading/Heading.stories.tsx +50 -0
  158. package/src/components/Heading/Heading.test.tsx +1 -0
  159. package/src/components/Heading/Heading.tsx +52 -0
  160. package/src/components/Heading/USAGE.md +36 -0
  161. package/src/components/HoverCard/HOVERCARD_IMPLEMENTATION_NOTES.md +77 -0
  162. package/src/components/HoverCard/HoverCard.module.css +90 -0
  163. package/src/components/HoverCard/HoverCard.stories.tsx +172 -0
  164. package/src/components/HoverCard/HoverCard.test.tsx +1 -0
  165. package/src/components/HoverCard/HoverCard.tsx +133 -0
  166. package/src/components/HoverCard/USAGE.md +52 -0
  167. package/src/components/Label/LABEL_IMPLEMENTATION_NOTES.md +48 -0
  168. package/src/components/Label/Label.module.css +141 -0
  169. package/src/components/Label/Label.stories.tsx +131 -0
  170. package/src/components/Label/Label.test.tsx +1 -0
  171. package/src/components/Label/Label.tsx +113 -0
  172. package/src/components/Label/USAGE.md +47 -0
  173. package/src/components/Label/index.ts +1 -0
  174. package/src/components/Layer/USAGE.md +54 -0
  175. package/src/components/Link/IMPLEMENTATION_NOTES.md +19 -0
  176. package/src/components/Link/Link.module.css +110 -0
  177. package/src/components/Link/Link.stories.tsx +58 -0
  178. package/src/components/Link/Link.test.tsx +1 -0
  179. package/src/components/Link/Link.tsx +67 -0
  180. package/src/components/Link/USAGE.md +56 -0
  181. package/src/components/Link/index.ts +1 -0
  182. package/src/components/Loader/LOADER_IMPLEMENTATION_NOTES.md +24 -0
  183. package/src/components/Loader/Loader.animate.dom.test.tsx +82 -0
  184. package/src/components/Loader/Loader.module.css +63 -0
  185. package/src/components/Loader/Loader.stories.tsx +123 -0
  186. package/src/components/Loader/Loader.test.tsx +1 -0
  187. package/src/components/Loader/Loader.tsx +69 -0
  188. package/src/components/Loader/USAGE.md +44 -0
  189. package/src/components/Menu/MENU_IMPLEMENTATION_NOTES.md +88 -0
  190. package/src/components/Menu/Menu.module.css +280 -0
  191. package/src/components/Menu/Menu.stories.tsx +371 -0
  192. package/src/components/Menu/Menu.test.tsx +1 -0
  193. package/src/components/Menu/Menu.tsx +373 -0
  194. package/src/components/Menu/USAGE.md +55 -0
  195. package/src/components/Modal/MODAL_IMPLEMENTATION_NOTES.md +32 -0
  196. package/src/components/Modal/Modal.module.css +302 -0
  197. package/src/components/Modal/Modal.stories.tsx +105 -0
  198. package/src/components/Modal/Modal.test.tsx +1 -0
  199. package/src/components/Modal/Modal.tsx +368 -0
  200. package/src/components/Modal/USAGE.md +58 -0
  201. package/src/components/Modal/index.ts +1 -0
  202. package/src/components/NumberInput/NUMBER_INPUT_IMPLEMENTATION_NOTES.md +24 -0
  203. package/src/components/NumberInput/NumberInput.module.css +302 -0
  204. package/src/components/NumberInput/NumberInput.stories.tsx +96 -0
  205. package/src/components/NumberInput/NumberInput.test.tsx +1 -0
  206. package/src/components/NumberInput/NumberInput.tsx +157 -0
  207. package/src/components/NumberInput/USAGE.md +52 -0
  208. package/src/components/Pagination/IMPLEMENTATION_NOTES.md +16 -0
  209. package/src/components/Pagination/Pagination.icons.tsx +66 -0
  210. package/src/components/Pagination/Pagination.module.css +188 -0
  211. package/src/components/Pagination/Pagination.stories.tsx +48 -0
  212. package/src/components/Pagination/Pagination.test.tsx +1 -0
  213. package/src/components/Pagination/Pagination.tsx +265 -0
  214. package/src/components/Pagination/USAGE.md +36 -0
  215. package/src/components/Pagination/index.ts +5 -0
  216. package/src/components/Panel/PANEL_IMPLEMENTATION_NOTES.md +116 -0
  217. package/src/components/Panel/Panel.module.css +209 -0
  218. package/src/components/Panel/Panel.stories.tsx +227 -0
  219. package/src/components/Panel/Panel.test.tsx +1 -0
  220. package/src/components/Panel/Panel.tsx +269 -0
  221. package/src/components/Panel/USAGE.md +56 -0
  222. package/src/components/Popover/IMPLEMENTATION_NOTES.md +77 -0
  223. package/src/components/Popover/Popover.module.css +86 -0
  224. package/src/components/Popover/Popover.stories.tsx +133 -0
  225. package/src/components/Popover/Popover.test.tsx +1 -0
  226. package/src/components/Popover/Popover.tsx +142 -0
  227. package/src/components/Popover/USAGE.md +61 -0
  228. package/src/components/Popover/index.ts +1 -0
  229. package/src/components/Radio/Radio.module.css +231 -0
  230. package/src/components/Radio/Radio.stories.tsx +93 -0
  231. package/src/components/Radio/Radio.test.tsx +1 -0
  232. package/src/components/Radio/Radio.tsx +168 -0
  233. package/src/components/Radio/RadioGroup.stories.tsx +117 -0
  234. package/src/components/Radio/RadioGroup.tsx +124 -0
  235. package/src/components/Radio/USAGE.md +36 -0
  236. package/src/components/ReadOnlyField/READ_ONLY_FIELD_IMPLEMENTATION_NOTES.md +15 -0
  237. package/src/components/ReadOnlyField/ReadOnlyBooleanField.tsx +21 -0
  238. package/src/components/ReadOnlyField/ReadOnlyField.module.css +54 -0
  239. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +163 -0
  240. package/src/components/ReadOnlyField/ReadOnlyField.test.tsx +1 -0
  241. package/src/components/ReadOnlyField/ReadOnlyField.tsx +114 -0
  242. package/src/components/ReadOnlyField/ReadOnlySwitchField.tsx +21 -0
  243. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +48 -0
  244. package/src/components/ReadOnlyField/USAGE.md +44 -0
  245. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +93 -0
  246. package/src/components/ReadOnlyField/index.ts +5 -0
  247. package/src/components/RecursicaThemeProvider/USAGE.md +60 -0
  248. package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +25 -0
  249. package/src/components/SegmentedControl/SegmentedControl.module.css +185 -0
  250. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +106 -0
  251. package/src/components/SegmentedControl/SegmentedControl.test.tsx +1 -0
  252. package/src/components/SegmentedControl/SegmentedControl.tsx +110 -0
  253. package/src/components/SegmentedControl/USAGE.md +55 -0
  254. package/src/components/Slider/IMPLEMENTATION_NOTES.md +92 -0
  255. package/src/components/Slider/Slider.module.css +588 -0
  256. package/src/components/Slider/Slider.stories.tsx +282 -0
  257. package/src/components/Slider/Slider.test.tsx +1 -0
  258. package/src/components/Slider/Slider.tsx +391 -0
  259. package/src/components/Slider/USAGE.md +56 -0
  260. package/src/components/Stack/STACK_IMPLEMENTATION_NOTES.md +4 -0
  261. package/src/components/Stack/Stack.module.css +7 -0
  262. package/src/components/Stack/Stack.stories.tsx +108 -0
  263. package/src/components/Stack/Stack.test.tsx +1 -0
  264. package/src/components/Stack/Stack.tsx +62 -0
  265. package/src/components/Stack/USAGE.md +48 -0
  266. package/src/components/Stepper/IMPLEMENTATION_NOTES.md +28 -0
  267. package/src/components/Stepper/Stepper.module.css +404 -0
  268. package/src/components/Stepper/Stepper.stories.tsx +148 -0
  269. package/src/components/Stepper/Stepper.test.tsx +1 -0
  270. package/src/components/Stepper/Stepper.tsx +93 -0
  271. package/src/components/Stepper/USAGE.md +41 -0
  272. package/src/components/Switch/SWITCH_IMPLEMENTATION_NOTES.md +64 -0
  273. package/src/components/Switch/Switch.module.css +279 -0
  274. package/src/components/Switch/Switch.stories.tsx +114 -0
  275. package/src/components/Switch/Switch.test.tsx +1 -0
  276. package/src/components/Switch/Switch.tsx +177 -0
  277. package/src/components/Switch/SwitchGroup.stories.tsx +132 -0
  278. package/src/components/Switch/SwitchGroup.tsx +127 -0
  279. package/src/components/Switch/USAGE.md +42 -0
  280. package/src/components/Table/TABLE_IMPLEMENTATION_NOTES.md +78 -0
  281. package/src/components/Table/Table.icons.tsx +40 -0
  282. package/src/components/Table/Table.module.css +444 -0
  283. package/src/components/Table/Table.stories.tsx +140 -0
  284. package/src/components/Table/Table.test.tsx +1 -0
  285. package/src/components/Table/Table.tsx +243 -0
  286. package/src/components/Table/USAGE.md +78 -0
  287. package/src/components/Tabs/IMPLEMENTATION_NOTES.md +27 -0
  288. package/src/components/Tabs/Tabs.module.css +591 -0
  289. package/src/components/Tabs/Tabs.stories.tsx +154 -0
  290. package/src/components/Tabs/Tabs.test.tsx +1 -0
  291. package/src/components/Tabs/Tabs.tsx +114 -0
  292. package/src/components/Tabs/USAGE.md +45 -0
  293. package/src/components/Text/TEXT_IMPLEMENTATION_NOTES.md +12 -0
  294. package/src/components/Text/Text.module.css +13 -0
  295. package/src/components/Text/Text.stories.tsx +75 -0
  296. package/src/components/Text/Text.test.tsx +1 -0
  297. package/src/components/Text/Text.tsx +53 -0
  298. package/src/components/Text/USAGE.md +40 -0
  299. package/src/components/TextArea/TEXTAREA_IMPLEMENTATION_NOTES.md +7 -0
  300. package/src/components/TextArea/TextArea.module.css +183 -0
  301. package/src/components/TextArea/TextArea.stories.tsx +94 -0
  302. package/src/components/TextArea/TextArea.test.tsx +1 -0
  303. package/src/components/TextArea/TextArea.tsx +137 -0
  304. package/src/components/TextArea/USAGE.md +48 -0
  305. package/src/components/TextField/TEXTFIELD_IMPLEMENTATION_NOTES.md +19 -0
  306. package/src/components/TextField/TextField.module.css +264 -0
  307. package/src/components/TextField/TextField.stories.tsx +162 -0
  308. package/src/components/TextField/TextField.test.tsx +1 -0
  309. package/src/components/TextField/TextField.tsx +139 -0
  310. package/src/components/TextField/USAGE.md +44 -0
  311. package/src/components/TimePicker/TIMEPICKER_IMPLEMENTATION_NOTES.md +119 -0
  312. package/src/components/TimePicker/TimePicker.module.css +291 -0
  313. package/src/components/TimePicker/TimePicker.stories.tsx +141 -0
  314. package/src/components/TimePicker/TimePicker.test.tsx +1 -0
  315. package/src/components/TimePicker/TimePicker.tsx +304 -0
  316. package/src/components/TimePicker/USAGE.md +57 -0
  317. package/src/components/Timeline/TIMELINE_IMPLEMENTATION_NOTES.md +13 -0
  318. package/src/components/Timeline/Timeline.module.css +373 -0
  319. package/src/components/Timeline/Timeline.stories.tsx +128 -0
  320. package/src/components/Timeline/Timeline.test.tsx +1 -0
  321. package/src/components/Timeline/Timeline.tsx +73 -0
  322. package/src/components/Timeline/TimelineItem.tsx +79 -0
  323. package/src/components/Timeline/USAGE.md +49 -0
  324. package/src/components/Toast/TOAST_IMPLEMENTATION_NOTES.md +39 -0
  325. package/src/components/Toast/Toast.module.css +184 -0
  326. package/src/components/Toast/Toast.stories.tsx +67 -0
  327. package/src/components/Toast/Toast.test.tsx +1 -0
  328. package/src/components/Toast/Toast.tsx +82 -0
  329. package/src/components/Toast/USAGE.md +50 -0
  330. package/src/components/Toast/index.ts +1 -0
  331. package/src/components/Tooltip/TOOLTIP_IMPLEMENTATION_NOTES.md +85 -0
  332. package/src/components/Tooltip/Tooltip.module.css +91 -0
  333. package/src/components/Tooltip/Tooltip.stories.tsx +165 -0
  334. package/src/components/Tooltip/Tooltip.test.tsx +1 -0
  335. package/src/components/Tooltip/Tooltip.tsx +117 -0
  336. package/src/components/Tooltip/USAGE.md +80 -0
  337. package/src/components/TransferList/TRANSFERLIST_IMPLEMENTATION_NOTES.md +111 -0
  338. package/src/components/TransferList/TransferList.module.css +182 -0
  339. package/src/components/TransferList/TransferList.stories.tsx +121 -0
  340. package/src/components/TransferList/TransferList.test.tsx +1 -0
  341. package/src/components/TransferList/TransferList.tsx +419 -0
  342. package/src/components/TransferList/USAGE.md +77 -0
  343. package/src/components/Tree/IMPLEMENTATION_NOTES.md +48 -0
  344. package/src/components/Tree/Tree.module.css +232 -0
  345. package/src/components/Tree/Tree.stories.tsx +104 -0
  346. package/src/components/Tree/Tree.test.tsx +1 -0
  347. package/src/components/Tree/Tree.tsx +220 -0
  348. package/src/components/Tree/USAGE.md +96 -0
  349. package/src/components/Tree/index.ts +1 -0
  350. package/src/components/index.ts +52 -0
  351. package/src/env.d.ts +7 -0
  352. package/src/index.ts +179 -0
  353. package/src/types/index.ts +9 -0
  354. package/src/types/mantine.d.ts +7 -0
  355. package/src/types/scss-modules.d.ts +9 -0
  356. package/src/utils/ColorSchemeWrapper.tsx +27 -0
  357. package/src/utils/RequireAccessibleLabel.ts +1 -0
  358. package/src/utils/copyToClipboard.ts +36 -0
  359. package/src/utils/filterStylingProps.test.ts +85 -0
  360. package/src/utils/filterStylingProps.ts +150 -0
  361. package/src/utils/index.ts +1 -0
  362. package/src/utils/renderRichOption.tsx +65 -0
@@ -0,0 +1,121 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { TransferList } from "./TransferList";
4
+ import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
5
+
6
+ type TransferListStoryProps = React.ComponentProps<typeof TransferList>;
7
+
8
+ const SAMPLE_DATA: TransferListStoryProps["defaultData"] = [
9
+ [
10
+ { value: "alpha", label: "Alpha" },
11
+ { value: "bravo", label: "Bravo" },
12
+ { value: "charlie", label: "Charlie" },
13
+ { value: "delta", label: "Delta" },
14
+ { value: "echo", label: "Echo" },
15
+ ],
16
+ [{ value: "foxtrot", label: "Foxtrot" }],
17
+ ];
18
+
19
+ const GROUPED_DATA: TransferListStoryProps["defaultData"] = [
20
+ [
21
+ { value: "apple", label: "Apple", group: "Fruit" },
22
+ { value: "banana", label: "Banana", group: "Fruit" },
23
+ { value: "carrot", label: "Carrot", group: "Vegetable" },
24
+ { value: "daikon", label: "Daikon", group: "Vegetable" },
25
+ { value: "eagle", label: "Eagle" },
26
+ ],
27
+ [],
28
+ ];
29
+
30
+ const meta: Meta<TransferListStoryProps> = {
31
+ title: "UI-Kit/TransferList",
32
+ component: TransferList,
33
+ tags: ["autodocs"],
34
+ parameters: {
35
+ docs: {
36
+ description: {
37
+ component:
38
+ "TransferList (dual listbox) lets users move items between two lists. Composes FormControlWrapper, TextField, Checkbox, CheckboxGroup, Badge, and Button.",
39
+ },
40
+ },
41
+ },
42
+ args: {
43
+ label: "Assign users",
44
+ assistiveText: "Move users into the selected list.",
45
+ defaultData: SAMPLE_DATA,
46
+ disabled: false,
47
+ required: false,
48
+ },
49
+ argTypes: {
50
+ disabled: {
51
+ control: "boolean",
52
+ },
53
+ ...formControlArgTypes,
54
+ sourceLabel: {
55
+ control: "text",
56
+ },
57
+ targetLabel: {
58
+ control: "text",
59
+ },
60
+ searchable: {
61
+ control: "boolean",
62
+ },
63
+ searchPlaceholder: {
64
+ control: "text",
65
+ },
66
+ },
67
+ };
68
+
69
+ export default meta;
70
+
71
+ type Story = StoryObj<TransferListStoryProps>;
72
+
73
+ export const Default: Story = {};
74
+
75
+ export const Grouped: Story = {
76
+ args: {
77
+ label: "Assign ingredients",
78
+ defaultData: GROUPED_DATA,
79
+ },
80
+ };
81
+
82
+ export const SideBySide: Story = {
83
+ args: {
84
+ formLayout: "side-by-side",
85
+ },
86
+ };
87
+
88
+ export const NoSearch: Story = {
89
+ args: {
90
+ label: "Assign users (no filtering)",
91
+ searchable: false,
92
+ },
93
+ };
94
+
95
+ export const StaticError: Story = {
96
+ args: {
97
+ error: "Select at least one user.",
98
+ defaultData: [[], SAMPLE_DATA![0]],
99
+ },
100
+ };
101
+
102
+ export const StaticDisabled: Story = {
103
+ args: {
104
+ disabled: true,
105
+ },
106
+ };
107
+
108
+ export const Empty: Story = {
109
+ args: {
110
+ label: "Assign users",
111
+ defaultData: [[], []],
112
+ },
113
+ };
114
+
115
+ export const ReadOnly: Story = {
116
+ args: {
117
+ label: "Assigned users",
118
+ defaultData: [[], SAMPLE_DATA![0]],
119
+ readOnly: true,
120
+ },
121
+ };
@@ -0,0 +1 @@
1
+ // Placeholder for future TransferList component tests. Add tests here if/when needed.
@@ -0,0 +1,419 @@
1
+ import { forwardRef, useCallback, useId, useMemo, useState } from "react";
2
+ import {
3
+ filterStylingProps,
4
+ type RecursicaOverStyled,
5
+ } from "../../utils/filterStylingProps";
6
+ import { type FormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
7
+ import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
8
+ import { Badge } from "../Badge/Badge";
9
+ import { Button } from "../Button/Button";
10
+ import { TextField } from "../TextField/TextField";
11
+ import { Checkbox } from "../Checkbox/Checkbox";
12
+ import { CheckboxGroup } from "../Checkbox/CheckboxGroup";
13
+ import styles from "./TransferList.module.css";
14
+
15
+ import {
16
+ type ReadOnlyControlProps,
17
+ type RecursicaTransferListProps as BaseRecursicaTransferListProps,
18
+ type RecursicaTransferListItem,
19
+ type RecursicaTransferListData,
20
+ } from "@recursica/adapter-common";
21
+
22
+ export type { RecursicaTransferListItem, RecursicaTransferListData };
23
+
24
+ export interface RecursicaTransferListProps
25
+ extends Omit<
26
+ FormControlWrapperProps,
27
+ | "children"
28
+ | "overStyled"
29
+ | "controlMaxWidth"
30
+ | "controlMinWidth"
31
+ | "onChange"
32
+ >,
33
+ ReadOnlyControlProps,
34
+ BaseRecursicaTransferListProps {
35
+ /** Disables the whole control */
36
+ disabled?: boolean;
37
+ }
38
+
39
+ export type TransferListProps = RecursicaOverStyled<RecursicaTransferListProps>;
40
+
41
+ /** Single/double chevron glyphs for the transfer buttons; flipped via CSS for the "left" direction
42
+ * so only one path needs to be maintained, same approach as Tree's `ExpandGlyph`. */
43
+ function ChevronIcon({ direction }: { direction: "left" | "right" }) {
44
+ return (
45
+ <svg
46
+ className={styles.chevron}
47
+ data-direction={direction}
48
+ viewBox="0 0 16 16"
49
+ width="1em"
50
+ height="1em"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ strokeWidth="2"
54
+ strokeLinecap="round"
55
+ strokeLinejoin="round"
56
+ aria-hidden="true"
57
+ >
58
+ <path d="M6 3l5 5-5 5" />
59
+ </svg>
60
+ );
61
+ }
62
+
63
+ function ChevronsIcon({ direction }: { direction: "left" | "right" }) {
64
+ return (
65
+ <svg
66
+ className={styles.chevron}
67
+ data-direction={direction}
68
+ viewBox="0 0 16 16"
69
+ width="1em"
70
+ height="1em"
71
+ fill="none"
72
+ stroke="currentColor"
73
+ strokeWidth="2"
74
+ strokeLinecap="round"
75
+ strokeLinejoin="round"
76
+ aria-hidden="true"
77
+ >
78
+ <path d="M3 3l5 5-5 5M9 3l5 5-5 5" />
79
+ </svg>
80
+ );
81
+ }
82
+
83
+ /** Splits items into grouped/ungrouped buckets, same shape Forge's reference groups by `group`. */
84
+ function groupItems(items: RecursicaTransferListItem[]) {
85
+ const groups: Record<string, RecursicaTransferListItem[]> = {};
86
+ const ungrouped: RecursicaTransferListItem[] = [];
87
+ items.forEach((item) => {
88
+ if (item.group) {
89
+ (groups[item.group] ??= []).push(item);
90
+ } else {
91
+ ungrouped.push(item);
92
+ }
93
+ });
94
+ return { groups, ungrouped };
95
+ }
96
+
97
+ export const TransferList = forwardRef<HTMLDivElement, TransferListProps>(
98
+ function TransferList(props, ref) {
99
+ const {
100
+ overStyled = false,
101
+ formLayout = "stacked",
102
+
103
+ // Label & Wrapper Maps
104
+ labelSize,
105
+ labelAlignment,
106
+ labelOptionalText,
107
+ labelWithEditIcon,
108
+ labelActionArea,
109
+ onLabelEditClick,
110
+
111
+ label,
112
+ assistiveText,
113
+ description,
114
+ helperText,
115
+ assistiveWithIcon,
116
+ error,
117
+ required,
118
+ id: userProvidedId,
119
+ readOnly,
120
+ readOnlyComponent,
121
+ emptyValueComponent,
122
+
123
+ data: controlledData,
124
+ defaultData,
125
+ onChange,
126
+ sourceLabel = "Available",
127
+ targetLabel = "Selected",
128
+ searchable = true,
129
+ searchPlaceholder = "Filter items...",
130
+ disabled = false,
131
+
132
+ className,
133
+ style,
134
+ ...rest
135
+ } = props;
136
+ const sanitizedProps = filterStylingProps(rest, overStyled);
137
+
138
+ const generatedId = useId();
139
+ const id = userProvidedId || `recursica-transfer-list-${generatedId}`;
140
+
141
+ // Manage internal state for uncontrolled mode
142
+ const [internalData, setInternalData] = useState<RecursicaTransferListData>(
143
+ () => defaultData ?? controlledData ?? [[], []],
144
+ );
145
+ const effectiveData =
146
+ controlledData !== undefined ? controlledData : internalData;
147
+
148
+ const handleChange = useCallback(
149
+ (newData: RecursicaTransferListData) => {
150
+ if (controlledData === undefined) {
151
+ setInternalData(newData);
152
+ }
153
+ onChange?.(newData);
154
+ },
155
+ [controlledData, onChange],
156
+ );
157
+
158
+ const [sourceSearch, setSourceSearch] = useState("");
159
+ const [targetSearch, setTargetSearch] = useState("");
160
+ const [sourceSelected, setSourceSelected] = useState<Set<string>>(
161
+ () => new Set(),
162
+ );
163
+ const [targetSelected, setTargetSelected] = useState<Set<string>>(
164
+ () => new Set(),
165
+ );
166
+
167
+ const filteredSource = useMemo(() => {
168
+ if (!sourceSearch) return effectiveData[0];
169
+ const query = sourceSearch.toLowerCase();
170
+ return effectiveData[0].filter((item) =>
171
+ item.label.toLowerCase().includes(query),
172
+ );
173
+ }, [effectiveData, sourceSearch]);
174
+
175
+ const filteredTarget = useMemo(() => {
176
+ if (!targetSearch) return effectiveData[1];
177
+ const query = targetSearch.toLowerCase();
178
+ return effectiveData[1].filter((item) =>
179
+ item.label.toLowerCase().includes(query),
180
+ );
181
+ }, [effectiveData, targetSearch]);
182
+
183
+ const transferToTarget = useCallback(() => {
184
+ if (sourceSelected.size === 0) return;
185
+ const newSource = effectiveData[0].filter(
186
+ (item) => !sourceSelected.has(item.value),
187
+ );
188
+ const moved = effectiveData[0].filter((item) =>
189
+ sourceSelected.has(item.value),
190
+ );
191
+ setSourceSelected(new Set());
192
+ handleChange([newSource, [...effectiveData[1], ...moved]]);
193
+ }, [effectiveData, sourceSelected, handleChange]);
194
+
195
+ const transferToSource = useCallback(() => {
196
+ if (targetSelected.size === 0) return;
197
+ const newTarget = effectiveData[1].filter(
198
+ (item) => !targetSelected.has(item.value),
199
+ );
200
+ const moved = effectiveData[1].filter((item) =>
201
+ targetSelected.has(item.value),
202
+ );
203
+ setTargetSelected(new Set());
204
+ handleChange([[...effectiveData[0], ...moved], newTarget]);
205
+ }, [effectiveData, targetSelected, handleChange]);
206
+
207
+ const transferAllToTarget = useCallback(() => {
208
+ if (effectiveData[0].length === 0) return;
209
+ setSourceSelected(new Set());
210
+ handleChange([[], [...effectiveData[1], ...effectiveData[0]]]);
211
+ }, [effectiveData, handleChange]);
212
+
213
+ const transferAllToSource = useCallback(() => {
214
+ if (effectiveData[1].length === 0) return;
215
+ setTargetSelected(new Set());
216
+ handleChange([[...effectiveData[0], ...effectiveData[1]], []]);
217
+ }, [effectiveData, handleChange]);
218
+
219
+ const toggleSourceItem = useCallback((value: string) => {
220
+ setSourceSelected((prev) => {
221
+ const next = new Set(prev);
222
+ if (next.has(value)) next.delete(value);
223
+ else next.add(value);
224
+ return next;
225
+ });
226
+ }, []);
227
+
228
+ const toggleTargetItem = useCallback((value: string) => {
229
+ setTargetSelected((prev) => {
230
+ const next = new Set(prev);
231
+ if (next.has(value)) next.delete(value);
232
+ else next.add(value);
233
+ return next;
234
+ });
235
+ }, []);
236
+
237
+ const renderPane = (
238
+ paneId: string,
239
+ paneLabel: string,
240
+ items: RecursicaTransferListItem[],
241
+ allItems: RecursicaTransferListItem[],
242
+ selected: Set<string>,
243
+ onToggle: (value: string) => void,
244
+ search: string,
245
+ onSearchChange: (value: string) => void,
246
+ ) => {
247
+ const { groups, ungrouped } = groupItems(items);
248
+ const groupNames = Object.keys(groups).sort();
249
+ const countText =
250
+ selected.size > 0
251
+ ? `${selected.size} / ${allItems.length}`
252
+ : `${allItems.length}`;
253
+
254
+ return (
255
+ <div className={styles.pane} data-pane={paneId}>
256
+ <div className={styles.paneHeader}>
257
+ <span>{paneLabel}</span>
258
+ <Badge>{countText}</Badge>
259
+ </div>
260
+
261
+ {searchable && (
262
+ <div className={styles.paneSearch}>
263
+ <TextField
264
+ value={search}
265
+ onChange={(e) => onSearchChange(e.currentTarget.value)}
266
+ placeholder={searchPlaceholder}
267
+ disabled={disabled}
268
+ aria-label={`Filter ${paneLabel.toLowerCase()}`}
269
+ />
270
+ </div>
271
+ )}
272
+
273
+ <div className={styles.paneList}>
274
+ {items.length === 0 && (
275
+ <div className={styles.emptyState}>No items</div>
276
+ )}
277
+
278
+ {ungrouped.length > 0 && (
279
+ <CheckboxGroup>
280
+ {ungrouped.map((item) => (
281
+ <Checkbox
282
+ key={item.value}
283
+ id={`${id}-${paneId}-${item.value}`}
284
+ label={item.label}
285
+ checked={selected.has(item.value)}
286
+ onChange={() => onToggle(item.value)}
287
+ disabled={disabled}
288
+ />
289
+ ))}
290
+ </CheckboxGroup>
291
+ )}
292
+
293
+ {groupNames.map((groupName) => (
294
+ <CheckboxGroup
295
+ key={groupName}
296
+ label={groupName}
297
+ labelSize="small"
298
+ >
299
+ {groups[groupName].map((item) => (
300
+ <Checkbox
301
+ key={item.value}
302
+ id={`${id}-${paneId}-${item.value}`}
303
+ label={item.label}
304
+ checked={selected.has(item.value)}
305
+ onChange={() => onToggle(item.value)}
306
+ disabled={disabled}
307
+ />
308
+ ))}
309
+ </CheckboxGroup>
310
+ ))}
311
+ </div>
312
+ </div>
313
+ );
314
+ };
315
+
316
+ const wrapperClass = className
317
+ ? `${styles.layoutOverride} ${className}`
318
+ : styles.layoutOverride;
319
+
320
+ return (
321
+ <WithReadOnlyWrapper
322
+ ref={ref}
323
+ formLayout={formLayout}
324
+ labelSize={labelSize}
325
+ labelAlignment={labelAlignment}
326
+ labelOptionalText={labelOptionalText}
327
+ labelWithEditIcon={labelWithEditIcon}
328
+ labelActionArea={labelActionArea}
329
+ onLabelEditClick={onLabelEditClick}
330
+ label={label}
331
+ assistiveText={assistiveText}
332
+ description={description}
333
+ helperText={helperText}
334
+ assistiveWithIcon={assistiveWithIcon}
335
+ error={error}
336
+ required={required}
337
+ id={id}
338
+ className={wrapperClass}
339
+ style={style}
340
+ overStyled={overStyled as true}
341
+ readOnly={readOnly}
342
+ readOnlyComponent={readOnlyComponent}
343
+ emptyValueComponent={emptyValueComponent}
344
+ readOnlyType="text"
345
+ readOnlyValue={effectiveData[1].map((item) => item.label)}
346
+ readOnlyNativeProps={props}
347
+ activeComponent={
348
+ <div
349
+ {...(sanitizedProps as Record<string, unknown>)}
350
+ className={styles.root}
351
+ data-disabled={disabled ? "true" : undefined}
352
+ data-error={error ? "true" : undefined}
353
+ >
354
+ <div className={styles.panes}>
355
+ {renderPane(
356
+ "source",
357
+ sourceLabel,
358
+ filteredSource,
359
+ effectiveData[0],
360
+ sourceSelected,
361
+ toggleSourceItem,
362
+ sourceSearch,
363
+ setSourceSearch,
364
+ )}
365
+
366
+ <div className={styles.transferColumn}>
367
+ <Button
368
+ variant="outline"
369
+ size="small"
370
+ icon={<ChevronsIcon direction="right" />}
371
+ aria-label={`Move all to ${targetLabel}`}
372
+ disabled={disabled || effectiveData[0].length === 0}
373
+ onClick={transferAllToTarget}
374
+ />
375
+ <Button
376
+ variant="outline"
377
+ size="small"
378
+ icon={<ChevronIcon direction="right" />}
379
+ aria-label={`Move selected to ${targetLabel}`}
380
+ disabled={disabled || sourceSelected.size === 0}
381
+ onClick={transferToTarget}
382
+ />
383
+ <Button
384
+ variant="outline"
385
+ size="small"
386
+ icon={<ChevronIcon direction="left" />}
387
+ aria-label={`Move selected to ${sourceLabel}`}
388
+ disabled={disabled || targetSelected.size === 0}
389
+ onClick={transferToSource}
390
+ />
391
+ <Button
392
+ variant="outline"
393
+ size="small"
394
+ icon={<ChevronsIcon direction="left" />}
395
+ aria-label={`Move all to ${sourceLabel}`}
396
+ disabled={disabled || effectiveData[1].length === 0}
397
+ onClick={transferAllToSource}
398
+ />
399
+ </div>
400
+
401
+ {renderPane(
402
+ "target",
403
+ targetLabel,
404
+ filteredTarget,
405
+ effectiveData[1],
406
+ targetSelected,
407
+ toggleTargetItem,
408
+ targetSearch,
409
+ setTargetSearch,
410
+ )}
411
+ </div>
412
+ </div>
413
+ }
414
+ />
415
+ );
416
+ },
417
+ );
418
+
419
+ TransferList.displayName = "TransferList";
@@ -0,0 +1,77 @@
1
+ # TransferList - Usage Guide
2
+
3
+ This document describes how to integrate and use the `TransferList` component in your projects
4
+ using `@recursica/adapter-mantine-v8`.
5
+
6
+ ---
7
+
8
+ ## 1. Import Reference
9
+
10
+ ```tsx
11
+ import { TransferList } from "@recursica/adapter-mantine-v8";
12
+ ```
13
+
14
+ ---
15
+
16
+ ## 2. Basic Example
17
+
18
+ ```tsx
19
+ import React from "react";
20
+ import { TransferList } from "@recursica/adapter-mantine-v8";
21
+
22
+ export default function Demo() {
23
+ return (
24
+ <TransferList
25
+ label="Assign users"
26
+ sourceLabel="Available"
27
+ targetLabel="Selected"
28
+ defaultData={[
29
+ [
30
+ { value: "1", label: "Item 1" },
31
+ { value: "2", label: "Item 2" },
32
+ ],
33
+ [{ value: "3", label: "Item 3" }],
34
+ ]}
35
+ onChange={(data) => console.log(data)}
36
+ />
37
+ );
38
+ }
39
+ ```
40
+
41
+ Items sharing a `group` field render under a `CheckboxGroup` heading in their pane:
42
+
43
+ ```tsx
44
+ <TransferList
45
+ label="Assign ingredients"
46
+ defaultData={[
47
+ [
48
+ { value: "apple", label: "Apple", group: "Fruit" },
49
+ { value: "carrot", label: "Carrot", group: "Vegetable" },
50
+ { value: "eagle", label: "Eagle" }, // ungrouped, renders above the groups
51
+ ],
52
+ [],
53
+ ]}
54
+ />
55
+ ```
56
+
57
+ Pass `data` instead of `defaultData` to control the selection yourself.
58
+
59
+ ---
60
+
61
+ ## 3. Design System Integration
62
+
63
+ All Recursica components in the `@recursica/adapter-mantine-v8` package adhere strictly to design
64
+ system spacing, scaling, and behavior patterns.
65
+
66
+ > [!IMPORTANT]
67
+ >
68
+ > - **Anti-override protection**: Rogue style injections (like inline `style` or arbitrary
69
+ > `className`) are automatically blocked by our prop layer unless `overStyled={true}` is
70
+ > explicitly provided.
71
+ > - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific
72
+ > visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
73
+ > - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in
74
+ > `recursica_variables_scoped.css` and mapped in the component's CSS module.
75
+ > - **Form control layout**: `label`, `formLayout` (`stacked`/`side-by-side`), `required`,
76
+ > `assistiveText`/`description`/`helperText`, and `error` all route through the shared
77
+ > `FormControlWrapper`, same as every other form control.
@@ -0,0 +1,48 @@
1
+ # Tree Implementation Notes
2
+
3
+ - **Fully custom `renderNode`, not Mantine's default row markup.** Mantine's `Tree` renders each node as `<li {...getStyles("node")}>{renderNode ? renderNode(payload) : <div {...elementProps}>{node.label}</div>}{subtree}</li>`. We always pass a custom `renderNode` (never rely on the fallback) so every visual property — box model, colors, typography, the expand chevron — is fully token-driven rather than inheriting Mantine's own `Tree.module.css` defaults (which include Mantine's own light/dark `[data-selected]` background using its own gray palette).
4
+
5
+ - **`elementProps` already carries the important behavioral wiring.** Mantine's `renderNode` payload includes an `elementProps` object with `onClick` (combined expand+select handler, respecting `expandOnClick`/`selectOnClick`), `data-selected`, `data-value`, and `data-hovered` (set on keyboard/mouse hover via Mantine's internal `hoveredNode` state). We spread these directly onto our own `.row` div rather than reimplementing click/keyboard behavior — only the `className` is overridden with our own token-driven class.
6
+
7
+ - **UPDATED (Matt Massey, 2026-08-08): the chevron is now a real `Button`, superseding the two notes below.** Forge's real design shows the chevron following Button's own styling rules (`variant="text"` `size="small"`) — see the new entry near the end of this file for the full swap and why it doesn't reopen the nested-focusable-element concern the original decision (kept below for history) was worried about.
8
+ - ~~The chevron is a decorative element inside the same clickable row, not a separate `<button>`.~~ The design tokens include a `button-node-gap` token (implying a "button" next to the node label in Figma), but structurally the whole row is already the click target via `elementProps.onClick` (which correctly bundles Mantine's `expandOnClick`/`selectOnClick` logic). Making the chevron its own nested interactive `<button>` inside an `<li>` that Mantine already treats as keyboard-focusable (`tabIndex`, arrow-key handling in `TreeNode.tsx`) would create nested-focusable-element accessibility problems for no real benefit. `button-node-gap` is used as the CSS `gap` between the (decorative) chevron and the label inside `.row` instead.
9
+
10
+ - ~~No dedicated icon-color/icon-size token exists for Tree.~~ The expand chevron is an inline SVG using `stroke="currentColor"`, sized at `1em` (relative to the row's own tokened `font-size`). This means it automatically tracks whichever text-color token (selected/unselected) is active on `.row` — no new token was invented, and no hardcoded color was used.
11
+
12
+ - **RESOLVED (2026-08-08) — KNOWN GAP (2026-08-06): Forge's own preview shows a red chevron with more chevron-to-label spacing than what's currently implemented here.** Explained by the Button swap below: Forge's chevron is Button's own "text" variant color (Recursica's red/alert-adjacent brand color), not a `tree`-namespace icon-color token — there never was a missing token to find. Re-verified against both the compiled `recursica_variables_scoped.css` and the raw `recursica_ui-kit.json` at the time: the `tree` schema genuinely has no `icon-color` property anywhere, and `button-node-gap` resolves to `brand.dimensions.general.sm` = 4px, exactly what `.row`'s `gap` uses — confirmed not a mapping bug on our side, just the wrong mental model (a hand-drawn glyph instead of an actual Button).
13
+
14
+ - **`--level-offset` override needs `!important`.** Mantine's `Tree` computes each node's indent via a `--level-offset` CSS variable, resolved internally through its `createVarsResolver` mechanism and applied as a scoped rule on the root `<ul>` (same category of override as `Timeline`'s `--mantine-spacing-xl` / `--tl-line-width`). We override it on `.root` with `!important` to reliably beat Mantine's own generated rule regardless of stylesheet insertion order, rather than passing a `levelOffset` prop from TSX (which would mean setting a design-token value from TSX, against `COMPONENT_DEV_GUIDE.md`'s "no custom properties set from TSX for styling" rule).
15
+
16
+ - **`onSelectedChange` is a Recursica addition, not a Mantine API.** Mantine's `useTree()` controller tracks `selectedState` but has no change callback of its own. We watch `tree.selectedState` with a `useEffect` and fire `onSelectedChange` when it changes. This is a behavioral callback, not a design-token reactivity concern, so it doesn't conflict with `COMPONENT_DEV_GUIDE.md §7`'s rule against using `useEffect` for token/styling reactivity.
17
+
18
+ - **Deliberately not implemented (no tokens back them):** checkbox/indeterminate node state (Mantine's `Tree` supports `checkNode`/`isNodeIndeterminate`, but the Figma UI Kit's `tree` tokens only define `selected`/`unselected`, not a checked state) and per-node `disabled` (no token, and no `disabled` field in Mantine's `TreeNodeData` either). If either is needed later, it needs a token/schema addition first, not a component-level workaround.
19
+
20
+ - **Keyboard nav (Matt Massey, 2026-08-08): arrow up/down already worked — only the focus ring was missing.** Mantine's own `TreeNode` already implements full roving-tabindex keyboard navigation (`ArrowUp`/`ArrowDown` move focus between rows, `ArrowLeft`/`ArrowRight` collapse/expand, `Space` toggles) — confirmed via reading `TreeNode.mjs` directly and verifying with Playwright that focus genuinely moves between rows on `ArrowDown`. The reason it looked broken: DOM focus lands on `.node` (the `<li role="treeitem">`), and neither `.node` nor `.row` had any focus style at all (`outline: none`, no box-shadow) — so keyboard navigation was invisible, and only `Space`/`ArrowLeft`/`ArrowRight` were noticeable at all, since those also cause a visible expand/collapse side effect. Fixed by adding a `:focus-visible` box-shadow ring (same tokens as every other focusable component), keyed off `.node:focus-visible` since that's where DOM focus actually lands. Originally drawn on `.row` (the whole button+label box); see the next entry for why it now targets `.label` only.
21
+
22
+ - **Chevron → `Button` (Matt Massey, 2026-08-08).** Forge's real design uses an actual `Button` (`variant="text"` `size="small"`, icon-only) for the expand/collapse chevron, not a hand-drawn glyph — see the resolved "KNOWN GAP" above. Swapped `ExpandGlyph`'s plain `<span>` wrapper for a real `<Button icon={<ExpandGlyph/>} tabIndex={-1} aria-hidden="true" .../>`:
23
+
24
+ - **Doesn't reopen the nested-focusable-element concern** the original (now-struck-through) decision above was worried about: the embedded Button is never independently focusable or tab-stoppable (`tabIndex={-1}`) and is hidden from assistive tech (`aria-hidden`) — the row (`.node`) stays the single focusable/interactive element. Confirmed `Button`/`UnstyledButton` don't call `stopPropagation()` on click, so clicking the chevron still bubbles up to the row's existing `elementProps.onClick` and triggers expand/collapse exactly as before — no `onClick` needed on the embedded Button itself.
25
+ - **Rendered for every row, including leaves**, so every row reserves identical layout space; `.row:not([data-has-children]) .expandButton { visibility: hidden; }` hides it on leaf rows without needing to duplicate Button's own size tokens for a placeholder.
26
+ - **Rotation targets the glyph directly, not Button's internals**: `ExpandGlyph`'s `<svg>` carries its own `styles.expandGlyph` class, targeted via `.row[data-expanded] .expandGlyph` — a plain descendant-combinator selector that reaches the glyph regardless of how deeply `Button` nests it internally (`Button`'s own `.iconWrapper` isn't reachable from `Tree.module.css` at all — CSS Modules don't expose a stable cross-file class name for it, unlike Mantine's own `mantine-*` global classes).
27
+ - **Focus ring narrowed to `.label` only, excluding the Button** (Matt: "the focus ring for an item should be around just the node, not including the chevron button" / "the chevron button should not have a focus state"). `.label` was given `flex: 1 1 auto; height: 100%` so the ring still reads as a clean box covering the row's remaining width, not a tight text-only outline.
28
+ - **Found and fixed a real, separate bug while embedding this**: `Button.tsx` (both adapters) explicitly sets `className={finalClass}` (merging `styles.root` with any caller className) but then spreads `{...sanitizedProps}` _after_ it — and `sanitizedProps` still contains the original, unmodified `className` key, since it was only _read_, never deleted. The later spread silently overwrote `finalClass` with just the caller's own class whenever one was passed (e.g. `styles.expandButton` here) — exact same bug class as `Dropdown.tsx`/`BareDropdown.tsx` had. In mui-adapter this was fully visible (the chevron rendered in MUI's own default blue, since `Button-module__root` — and therefore every `[data-variant]` color rule — was missing entirely). In adapter-mantine-v8 it happened to be masked: Mantine's `classNames={{root: ...}}` object prop is separate from the plain `className` string and unaffected by the bug, so `Button-module__root` still applied via that path — but the underlying bug was there too, and is now fixed in both.
29
+
30
+ - **Expand/collapse and select made fully independent (Matt Massey, 2026-08-10), superseding the click-bubbling behavior described above.** Previously the chevron was purely decorative and let clicks bubble up to the row's combined expand+select handler; Matt clarified the real requirement: the button must be the _only_ way to expand/collapse on click, a row click must _only_ select, `Enter`/`Space` must _only_ select (never expand, even on a node with children), and `ArrowLeft`/`ArrowRight` must _only_ expand/collapse (never select) — letting a user toggle a subtree open without ever changing selection, and vice versa. Removed the now ill-fitting `expandOnClick`/`selectOnClick` props entirely (per point 5 of that request) — the pattern is fixed, not configurable.
31
+
32
+ - **Click**: `<MantineTree>` is now called with fixed `expandOnClick={false}` `selectOnClick={true}` (so `elementProps.onClick`, spread onto `.row`, only ever selects), and the embedded chevron `Button` now has its own `onClick` that calls `event.stopPropagation()` (so the click never also reaches `.row`'s select handler) and `tree.toggleExpanded(node.value)` directly via the controller — using `tree`/`node`, both already present in `renderNode`'s payload.
33
+ - **Enter/Space**: Mantine's `TreeNode` has no `Enter` handling and only wires `Space` to `toggleExpanded` (`expandOnSpace`, now passed as `false`) — no select-on-key behavior exists in the library at all, and the key handler lives on the `<li role="treeitem">` itself, unreachable through `renderNode`'s `elementProps`. Added a native `keydown` listener on the tree's root `<ul>` (merged via `useMergedRef` with the forwarded `ref`) that calls `tree.select(value)` on `Enter`/`Space`, reading the focused node's value off `event.target`'s own `data-value` (already set there by Mantine). `ArrowLeft`/`ArrowRight` needed no changes — Mantine's own handling already only calls `controller.expand`/`collapse`, never `select`.
34
+ - **Background/focus scoped to the label, not the whole node** (Matt: "The focus/background color should be on the node's label, not the entire node"): moved every visual property (padding, border, font, unselected/selected colors, hover overlay) off `.row` and onto `.label` — `.row` is now a plain flex layout container (indentation + `button-node-gap` only). `data-selected` is read directly off `renderNode`'s own `selected` payload field and applied to `.label` itself (no need to also spread `elementProps`'s copy there); `data-hovered`/`:hover` are still only observable on `.row` (from `elementProps`), so those states reach `.label` via `.row:hover .label`/`.row[data-hovered] .label` descendant selectors instead. The focus ring (added in the entry above) already targeted `.label` only, so it needed no further change.
35
+
36
+ - **Three follow-on fixes to the label-scoping work above (Matt Massey, 2026-08-10):**
37
+
38
+ - **Selected/hover chip was full-row width, not label width.** `.label` had `flex: 1 1 auto` — the `1` flex-grow stretched it to fill `.row`'s entire remaining width (button aside), so the highlighted chip visually covered the whole row even though the _properties_ were correctly scoped to `.label`. Changed to `flex: 0 1 auto` (no grow, same shrink/basis) so the chip sizes to its own content, matching Forge.
39
+ - **Focusing an expanded parent drew the ring on every descendant label too.** `.node:focus-visible .label` is a plain descendant selector — every child node's `.label` (inside the nested `.subtree` `<ul>`) is _also_ a descendant of the focused parent's `.node` `<li>`, at any depth, so the ring matched all of them at once. Fixed by scoping to `.node:focus-visible > .row .label`: `.row` is always a direct child of its own `.node`, but never of an ancestor `.node` (those reach it through the intervening `.subtree` `<ul>` and nested `<li>` instead) — the `>` combinator excludes every one of them.
40
+ - No MUI-style leftover "default selected background" issue existed here — Mantine's own `Tree` has no default row styling of its own once a custom `renderNode` is supplied (see the very first entry in this file), so there was nothing to neutralize on this side.
41
+
42
+ - **Whole-tree `disabled` (Matt Massey, 2026-08-10), added to support a `Disabled` story.** Mantine's `Tree`/`useTree`/`TreeNode` have no `disabled` concept anywhere in their API, unlike `@mui/x-tree-view` (which already had per-item `disabled` plumbing sitting mostly unused — see mui-adapter's own note on this). Per-node disabling still isn't exposed (no token, no `disabled` field on `RecursicaTreeNode` — same reasoning as the existing "Deliberately not implemented" entry above), only a single whole-tree toggle.
43
+
44
+ - **Mouse**: `selectOnClick={!disabled}` reuses Mantine's own flag for row clicks. The chevron `Button`'s `onClick` bypasses that flag entirely (calls `tree.toggleExpanded` directly), so it needs its own explicit `if (disabled) return;` guard. `.root[data-disabled] { pointer-events: none; }` is a second, CSS-only backstop covering both at once — belt-and-suspenders, not strictly required given the two guards above, but consistent with how little the library gives us to rely on here.
45
+ - **Keyboard**: our own `Enter`/`Space` → `select` listener (added in the entry above) just checks `disabled` at the top now. `ArrowLeft`/`ArrowRight` expand/collapse is baked into Mantine's own `TreeNode.handleKeyDown`, on the `<li>` itself, with no prop to disable it — the only reachable way to block it is a _second_, capture-phase `keydown` listener on the tree root that unconditionally calls `stopPropagation()` when disabled. Since capture fires before the event ever reaches its target (the focused `<li>`), this keeps Mantine's internal handler — and our own bubble-phase listener on the same root — from ever running, without needing to fork `TreeNode`.
46
+ - **Visual**: `opacity: var(--recursica_brand_states_disabled)` on `.root` — the generic disabled token, same convention used everywhere else in the design system for components without a dedicated disabled token (no `tree`-specific one exists). A selected node's chip stays visible underneath, just dimmed along with everything else, per Matt's ask to verify that combination looks right.
47
+
48
+ - **Chevron `Button` could still steal DOM focus despite `tabIndex={-1}` (Matt Massey, 2026-08-24), caught via a live browser console warning.** The embedded chevron `Button`'s `tabIndex={-1}`/`aria-hidden` combination (added when the chevron became a real `Button` — see above) only removes it from the sequential tab order; it doesn't stop a native `<button>` from receiving DOM focus on a direct mouse click, which browsers still do regardless of `tabIndex`. Since the button also has `aria-hidden="true"`, a click on it left an aria-hidden element holding focus — flagged by Chrome as "Blocked aria-hidden on an element because its descendant retained focus." Fixed with `onMouseDown={(event) => event.preventDefault()}` on the same `Button`: this suppresses the browser's default click-focuses-the-target behavior without affecting the `onClick` handler (`click` still fires normally on mouseup). Same fix applied to mui-adapter's `ExpandToggleButton`.