@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,354 @@
1
+ import React, { forwardRef, useEffect, useRef, useState } from "react";
2
+ import { type InputWrapperProps } from "@mantine/core";
3
+ import {
4
+ filterStylingProps,
5
+ type RecursicaOverStyled,
6
+ } from "../../utils/filterStylingProps";
7
+ import { Button } from "../Button/Button";
8
+ import { Chip } from "../Chip/Chip";
9
+ import {
10
+ FormControlWrapper,
11
+ type RecursicaFormControlWrapperProps,
12
+ } from "../FormControlWrapper/FormControlWrapper";
13
+ import styles from "./FileUpload.module.css";
14
+
15
+ import {
16
+ fileMatchesAccept,
17
+ type RecursicaFileUploadItem,
18
+ type RecursicaFileUploadProps as BaseRecursicaFileUploadProps,
19
+ } from "@recursica/adapter-common";
20
+ export type { RecursicaFileUploadItem };
21
+
22
+ export interface RecursicaFileUploadProps
23
+ extends Omit<
24
+ React.HTMLAttributes<HTMLDivElement>,
25
+ "children" | "onDrop" | "onChange"
26
+ >,
27
+ Pick<
28
+ InputWrapperProps,
29
+ "label" | "error" | "required" | "withAsterisk" | "id"
30
+ >,
31
+ Omit<
32
+ RecursicaFormControlWrapperProps,
33
+ "controlMaxWidth" | "controlMinWidth"
34
+ >,
35
+ BaseRecursicaFileUploadProps {}
36
+
37
+ export type FileUploadProps = RecursicaOverStyled<RecursicaFileUploadProps>;
38
+
39
+ function UploadIcon() {
40
+ return (
41
+ <svg
42
+ xmlns="http://www.w3.org/2000/svg"
43
+ viewBox="0 0 24 24"
44
+ fill="none"
45
+ stroke="currentColor"
46
+ strokeWidth="2"
47
+ strokeLinecap="round"
48
+ strokeLinejoin="round"
49
+ aria-hidden
50
+ >
51
+ <path d="M12 3v12" />
52
+ <path d="M7 8l5-5 5 5" />
53
+ <path d="M4 21h16" />
54
+ </svg>
55
+ );
56
+ }
57
+
58
+ /**
59
+ * A dropzone for uploading files, with a native browse-button fallback and a
60
+ * removable-chip list of the currently selected files.
61
+ *
62
+ * @example
63
+ * <FileUpload
64
+ * label="Upload Files"
65
+ * assistiveText="Max file size 5MB"
66
+ * files={files}
67
+ * onFilesAdded={(added) => setFiles((f) => [...f, ...added.map((file) => ({ file }))])}
68
+ * onFileRemove={(id) => setFiles((f) => f.filter((item) => (item.id ?? item.file.name) !== id))}
69
+ * />
70
+ */
71
+ export const FileUpload = forwardRef<HTMLDivElement, FileUploadProps>(
72
+ function FileUpload(props, ref) {
73
+ const {
74
+ overStyled = false,
75
+ formLayout = "stacked",
76
+
77
+ // Label & Wrapper Maps
78
+ labelSize,
79
+ labelAlignment,
80
+ labelOptionalText,
81
+ labelWithEditIcon,
82
+ labelActionArea,
83
+ onLabelEditClick,
84
+
85
+ label,
86
+ assistiveText,
87
+ assistiveWithIcon,
88
+ error,
89
+ required,
90
+ withAsterisk,
91
+ id,
92
+ className,
93
+ style,
94
+ disabled,
95
+ readOnly,
96
+
97
+ files,
98
+ onFilesAdded,
99
+ onFileRemove,
100
+ accept,
101
+ multiple = true,
102
+ maxSize,
103
+ maxFiles,
104
+ onFilesRejected,
105
+ invalidFileTypeMessage = "File type not accepted",
106
+ maxFilesMessage = `Maximum of ${maxFiles} files allowed`,
107
+ icon,
108
+ dropzoneLabel = "Drag and drop files here to upload",
109
+ browseButtonLabel = "Browse files",
110
+ removeFileLabel = "Remove",
111
+ ...rest
112
+ } = props;
113
+
114
+ const sanitizedProps = filterStylingProps(rest, overStyled);
115
+ const restRecord = sanitizedProps as Record<string, unknown>;
116
+
117
+ const inputRef = useRef<HTMLInputElement>(null);
118
+
119
+ // Whether the most recent drop/pick attempt included a file that failed the `accept` check —
120
+ // surfaced as the control's error state (see `effectiveError` below) rather than left for the
121
+ // integrator to wire up themselves.
122
+ const [invalidTypeRejected, setInvalidTypeRejected] = useState(false);
123
+ // Whether the most recent drop/pick attempt included a file that would have pushed the total
124
+ // past `maxFiles` — surfaced the same way as `invalidTypeRejected` (see `effectiveError`).
125
+ const [tooManyFilesRejected, setTooManyFilesRejected] = useState(false);
126
+
127
+ const handleFiles = (incoming: FileList | File[]) => {
128
+ if (disabled) return;
129
+ const list = Array.from(incoming);
130
+ if (list.length === 0) return;
131
+
132
+ const currentCount = files?.length ?? 0;
133
+ const accepted: File[] = [];
134
+ const rejected: File[] = [];
135
+ let hasInvalidType = false;
136
+ let hasTooMany = false;
137
+ for (const file of list) {
138
+ const isInvalidType = !fileMatchesAccept(file, accept);
139
+ if (isInvalidType) hasInvalidType = true;
140
+ const isTooLarge = maxSize !== undefined && file.size > maxSize;
141
+ const wouldExceedMax =
142
+ maxFiles !== undefined && currentCount + accepted.length >= maxFiles;
143
+ if (wouldExceedMax) hasTooMany = true;
144
+ const isRejected = isInvalidType || isTooLarge || wouldExceedMax;
145
+ (isRejected ? rejected : accepted).push(file);
146
+ }
147
+ setInvalidTypeRejected(hasInvalidType);
148
+ setTooManyFilesRejected(hasTooMany);
149
+ if (accepted.length > 0) onFilesAdded?.(accepted);
150
+ if (rejected.length > 0) onFilesRejected?.(rejected);
151
+ };
152
+
153
+ // Counts nested dragenter/dragleave pairs (they fire for every child element the pointer
154
+ // crosses, not just the dropzone itself) so the drag-over visual state only clears once the
155
+ // pointer has actually left the dropzone, not just moved between its children.
156
+ const dragCounterRef = useRef(0);
157
+ const [isDragging, setIsDragging] = useState(false);
158
+
159
+ const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
160
+ event.preventDefault();
161
+ dragCounterRef.current += 1;
162
+ setIsDragging(true);
163
+ };
164
+
165
+ const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
166
+ event.preventDefault();
167
+ dragCounterRef.current -= 1;
168
+ if (dragCounterRef.current <= 0) {
169
+ dragCounterRef.current = 0;
170
+ setIsDragging(false);
171
+ }
172
+ };
173
+
174
+ const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
175
+ // Required so the browser treats this element as a valid drop target.
176
+ event.preventDefault();
177
+ };
178
+
179
+ const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
180
+ event.preventDefault();
181
+ dragCounterRef.current = 0;
182
+ setIsDragging(false);
183
+ handleFiles(event.dataTransfer.files);
184
+ };
185
+
186
+ const openFilePicker = () => {
187
+ if (disabled) return;
188
+ inputRef.current?.click();
189
+ };
190
+
191
+ const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
192
+ if (event.target.files) handleFiles(event.target.files);
193
+ // Reset so picking the same file again still fires a change event.
194
+ event.target.value = "";
195
+ };
196
+
197
+ // Roving tabindex across the file chip list: only the "active" chip's delete icon is a tab
198
+ // stop (Tab lands on the first chip), and Left/Right/Up/Down move it — see
199
+ // FILEUPLOAD_IMPLEMENTATION_NOTES.md.
200
+ const [activeChipIndex, setActiveChipIndex] = useState(0);
201
+ const deleteIconRefs = useRef<Array<HTMLSpanElement | null>>([]);
202
+ const prevFileCountRef = useRef(files?.length ?? 0);
203
+
204
+ useEffect(() => {
205
+ const count = files?.length ?? 0;
206
+ // A chip was removed (via keyboard or otherwise) — keep focus in the list, clamped to the
207
+ // new length, rather than letting it fall back to the document body.
208
+ if (count > 0 && count < prevFileCountRef.current) {
209
+ const nextIndex = Math.min(activeChipIndex, count - 1);
210
+ setActiveChipIndex(nextIndex);
211
+ deleteIconRefs.current[nextIndex]?.focus();
212
+ }
213
+ prevFileCountRef.current = count;
214
+ // Only react to the file list itself shrinking/growing, not to activeChipIndex changes.
215
+ // eslint-disable-next-line react-hooks/exhaustive-deps
216
+ }, [files]);
217
+
218
+ const handleFileListKeyDown = (
219
+ event: React.KeyboardEvent<HTMLDivElement>,
220
+ ) => {
221
+ const count = files?.length ?? 0;
222
+ if (count === 0) return;
223
+ let nextIndex: number | undefined;
224
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
225
+ nextIndex = (activeChipIndex + 1) % count;
226
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
227
+ nextIndex = (activeChipIndex - 1 + count) % count;
228
+ }
229
+ if (nextIndex === undefined) return;
230
+ event.preventDefault();
231
+ setActiveChipIndex(nextIndex);
232
+ deleteIconRefs.current[nextIndex]?.focus();
233
+ };
234
+
235
+ // The built-in `accept`-mismatch message is only shown when the integrator hasn't supplied
236
+ // their own `error` — an explicit error always wins.
237
+ const effectiveError =
238
+ error ??
239
+ (invalidTypeRejected
240
+ ? invalidFileTypeMessage
241
+ : tooManyFilesRejected
242
+ ? maxFilesMessage
243
+ : undefined);
244
+
245
+ const wrapperClass = className
246
+ ? `${styles.layoutOverride} ${className}`
247
+ : styles.layoutOverride;
248
+
249
+ return (
250
+ <FormControlWrapper
251
+ overStyled={overStyled as true}
252
+ className={wrapperClass}
253
+ style={style}
254
+ formLayout={formLayout}
255
+ labelSize={labelSize}
256
+ labelAlignment={labelAlignment}
257
+ labelOptionalText={labelOptionalText}
258
+ labelWithEditIcon={labelWithEditIcon}
259
+ labelActionArea={labelActionArea}
260
+ onLabelEditClick={onLabelEditClick}
261
+ label={label}
262
+ assistiveText={assistiveText}
263
+ assistiveWithIcon={assistiveWithIcon}
264
+ error={effectiveError}
265
+ required={required}
266
+ withAsterisk={withAsterisk}
267
+ id={id}
268
+ >
269
+ <div
270
+ ref={ref}
271
+ {...restRecord}
272
+ className={styles.root}
273
+ data-disabled={disabled ? "true" : undefined}
274
+ data-error={effectiveError ? "true" : undefined}
275
+ >
276
+ {!readOnly && (
277
+ <div
278
+ className={styles.dropzone}
279
+ data-dragging={isDragging ? "true" : undefined}
280
+ onDragEnter={disabled ? undefined : handleDragEnter}
281
+ onDragLeave={disabled ? undefined : handleDragLeave}
282
+ onDragOver={disabled ? undefined : handleDragOver}
283
+ onDrop={disabled ? undefined : handleDrop}
284
+ >
285
+ <span className={styles.uploadIcon}>
286
+ {icon ?? <UploadIcon />}
287
+ </span>
288
+ <span className={styles.dropzoneText}>{dropzoneLabel}</span>
289
+ <Button
290
+ variant="outline"
291
+ disabled={disabled}
292
+ onClick={openFilePicker}
293
+ >
294
+ {browseButtonLabel}
295
+ </Button>
296
+ <input
297
+ ref={inputRef}
298
+ type="file"
299
+ hidden
300
+ accept={accept}
301
+ multiple={multiple}
302
+ disabled={disabled}
303
+ onChange={handleInputChange}
304
+ />
305
+ </div>
306
+ )}
307
+
308
+ {files &&
309
+ files.length > 0 &&
310
+ (readOnly ? (
311
+ <div className={styles.fileList}>
312
+ {files.map((item: RecursicaFileUploadItem) => {
313
+ const itemId = item.id ?? item.file.name;
314
+ return (
315
+ <Chip key={itemId} checked={false} tabIndex={-1}>
316
+ {item.file.name}
317
+ </Chip>
318
+ );
319
+ })}
320
+ </div>
321
+ ) : (
322
+ <div
323
+ className={styles.fileList}
324
+ onKeyDown={handleFileListKeyDown}
325
+ >
326
+ {files.map((item: RecursicaFileUploadItem, index) => {
327
+ const itemId = item.id ?? item.file.name;
328
+ return (
329
+ <Chip
330
+ key={itemId}
331
+ checked={false}
332
+ tabIndex={-1}
333
+ deleteLabel={removeFileLabel}
334
+ deleteTabIndex={index === activeChipIndex ? 0 : -1}
335
+ deleteIconRef={(el) => {
336
+ deleteIconRefs.current[index] = el;
337
+ }}
338
+ onDelete={
339
+ disabled ? undefined : () => onFileRemove?.(itemId)
340
+ }
341
+ >
342
+ {item.file.name}
343
+ </Chip>
344
+ );
345
+ })}
346
+ </div>
347
+ ))}
348
+ </div>
349
+ </FormControlWrapper>
350
+ );
351
+ },
352
+ );
353
+
354
+ FileUpload.displayName = "FileUpload";
@@ -0,0 +1,199 @@
1
+ # FileUpload - Usage Guide
2
+
3
+ This document describes how to integrate and use the `FileUpload` component in your projects using `@recursica/adapter-mantine-v8`.
4
+
5
+ ---
6
+
7
+ ## 1. Import Reference
8
+
9
+ ```tsx
10
+ import { FileUpload } from "@recursica/adapter-mantine-v8";
11
+ ```
12
+
13
+ ---
14
+
15
+ ## 2. Basic Example
16
+
17
+ `FileUpload` is a **controlled** component: it never stores the selected files itself. `onFilesAdded` reports newly dropped/picked files, `onFileRemove` reports which file was removed, and you own the `files` array in between.
18
+
19
+ ```tsx
20
+ import React, { useState } from "react";
21
+ import { FileUpload } from "@recursica/adapter-mantine-v8";
22
+ import { type RecursicaFileUploadItem } from "@recursica/adapter-common";
23
+
24
+ export default function Demo() {
25
+ const [files, setFiles] = useState<RecursicaFileUploadItem[]>([]);
26
+
27
+ return (
28
+ <FileUpload
29
+ label="Upload Files"
30
+ assistiveText="Max file size 5MB"
31
+ files={files}
32
+ onFilesAdded={(added) =>
33
+ setFiles((prev) => [...prev, ...added.map((file) => ({ file }))])
34
+ }
35
+ onFileRemove={(id) =>
36
+ setFiles((prev) =>
37
+ prev.filter((item) => (item.id ?? item.file.name) !== id),
38
+ )
39
+ }
40
+ />
41
+ );
42
+ }
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 3. Props Reference
48
+
49
+ | Prop | Type | Description |
50
+ | ------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
51
+ | `files` | `RecursicaFileUploadItem[]` | Files currently selected, rendered as removable chips below the dropzone. Each item is `{ file: File; id?: string }`. |
52
+ | `onFilesAdded` | `(files: File[]) => void` | Called with newly dropped/picked files. Only the new files — merge them into `files` yourself. |
53
+ | `onFileRemove` | `(id: string) => void` | Called with a file's `id` (or `file.name` if no `id` was given) when its remove (X) icon is activated. |
54
+ | `accept` | `string` | Native `accept` attribute (e.g. `".pdf,.png"` or `"image/*"`) — constrains the picker dialog, and is also enforced against dropped files (via `onFilesRejected`), since the browser never applies `accept` to a `drop` event itself. |
55
+ | `multiple` | `boolean` | Whether more than one file can be selected/dropped at once. Defaults to `true`. |
56
+ | `maxSize` | `number` | Maximum size per file, in bytes. Oversized files go to `onFilesRejected` instead of `onFilesAdded`. |
57
+ | `maxFiles` | `number` | Maximum total number of files allowed in `files`. Files that would exceed it go to `onFilesRejected` instead of `onFilesAdded`. |
58
+ | `onFilesRejected` | `(files: File[]) => void` | Called with files rejected for exceeding `maxSize`/`maxFiles` or not matching `accept`. |
59
+ | `invalidFileTypeMessage` | `React.ReactNode` | Error message shown when a file is rejected for not matching `accept`. Defaults to `"File type not accepted"`. An explicit `error` prop always takes priority over this. |
60
+ | `maxFilesMessage` | `React.ReactNode` | Error message shown when a file is rejected for exceeding `maxFiles`. Defaults to `"Maximum of {maxFiles} files allowed"`. An explicit `error` prop always takes priority over this. |
61
+ | `icon` | `React.ReactNode` | Icon shown above the dropzone label. Defaults to the built-in upload icon. |
62
+ | `dropzoneLabel` | `React.ReactNode` | Text shown inside the dropzone. Defaults to `"Drag and drop files here to upload"`. |
63
+ | `browseButtonLabel` | `React.ReactNode` | Label for the button that opens the native file picker. Defaults to `"Browse files"`. |
64
+ | `removeFileLabel` | `string` | Screen-reader label for each file chip's remove button. Defaults to `"Remove"`. |
65
+ | `disabled` | `boolean` | Disables the dropzone, browse button, and every file chip's remove icon. |
66
+ | `readOnly` | `boolean` | Renders `files` as a static chip list with no remove icon, and omits the dropzone/browse button entirely. |
67
+
68
+ `FileUpload` also accepts the standard Recursica form-control props (`label`, `assistiveText`, `error`, `required`, `withAsterisk`, `formLayout`, `labelSize`, `labelAlignment`, `labelOptionalText`, `labelWithEditIcon`, `onLabelEditClick`) — see [FormControlWrapper's USAGE.md](../FormControlWrapper/USAGE.md) for how these behave.
69
+
70
+ ---
71
+
72
+ ## 4. Rejecting Oversized Files
73
+
74
+ ```tsx
75
+ <FileUpload
76
+ label="Upload Files"
77
+ assistiveText="Max file size 5MB"
78
+ maxSize={5 * 1024 * 1024}
79
+ files={files}
80
+ onFilesAdded={(added) =>
81
+ setFiles((prev) => [...prev, ...added.map((file) => ({ file }))])
82
+ }
83
+ onFilesRejected={(rejected) =>
84
+ setError(`${rejected.length} file(s) exceeded the 5MB limit.`)
85
+ }
86
+ onFileRemove={(id) =>
87
+ setFiles((prev) =>
88
+ prev.filter((item) => (item.id ?? item.file.name) !== id),
89
+ )
90
+ }
91
+ />
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 5. Rejecting Files by Extension/MIME Type
97
+
98
+ ```tsx
99
+ <FileUpload
100
+ label="Upload Files"
101
+ assistiveText="Only .pdf and .png files are accepted"
102
+ accept=".pdf,.png"
103
+ files={files}
104
+ onFilesAdded={(added) =>
105
+ setFiles((prev) => [...prev, ...added.map((file) => ({ file }))])
106
+ }
107
+ onFileRemove={(id) =>
108
+ setFiles((prev) =>
109
+ prev.filter((item) => (item.id ?? item.file.name) !== id),
110
+ )
111
+ }
112
+ />
113
+ ```
114
+
115
+ This applies equally to a file picked via "Browse files" and one dragged directly onto the
116
+ dropzone — the browser's own `accept` filtering never covers drag-and-drop, so `FileUpload`
117
+ re-validates it itself before calling `onFilesAdded`/`onFilesRejected`.
118
+
119
+ A mismatched file automatically puts the control into its error state with the message
120
+ `"File type not accepted"` — no need to wire `onFilesRejected` into your own `error` prop just to
121
+ show something. Override the message with `invalidFileTypeMessage`, or pass your own `error` prop
122
+ to take over the error state entirely:
123
+
124
+ ```tsx
125
+ <FileUpload
126
+ label="Upload Files"
127
+ accept=".pdf,.png"
128
+ invalidFileTypeMessage="Only PDF and PNG files are supported"
129
+ files={files}
130
+ onFilesAdded={(added) =>
131
+ setFiles((prev) => [...prev, ...added.map((file) => ({ file }))])
132
+ }
133
+ onFileRemove={(id) =>
134
+ setFiles((prev) =>
135
+ prev.filter((item) => (item.id ?? item.file.name) !== id),
136
+ )
137
+ }
138
+ />
139
+ ```
140
+
141
+ ---
142
+
143
+ ## 6. Limiting the Number of Files
144
+
145
+ ```tsx
146
+ <FileUpload
147
+ label="Upload Files"
148
+ assistiveText="Up to 2 files allowed"
149
+ maxFiles={2}
150
+ files={files}
151
+ onFilesAdded={(added) =>
152
+ setFiles((prev) => [...prev, ...added.map((file) => ({ file }))])
153
+ }
154
+ onFileRemove={(id) =>
155
+ setFiles((prev) =>
156
+ prev.filter((item) => (item.id ?? item.file.name) !== id),
157
+ )
158
+ }
159
+ />
160
+ ```
161
+
162
+ Once `files` reaches `maxFiles`, further dropped/picked files are rejected the same way an
163
+ `accept` mismatch is — automatically switching the control into its error state with the default
164
+ `maxFilesMessage` ("Maximum of 2 files allowed" for the example above). Override the message with
165
+ `maxFilesMessage`, or pass your own `error` prop to take over the error state entirely.
166
+
167
+ ---
168
+
169
+ ## 7. Read-only Mode
170
+
171
+ ```tsx
172
+ <FileUpload label="Uploaded Files" readOnly files={files} />
173
+ ```
174
+
175
+ Renders `files` as a plain, non-removable chip list with no dropzone or Browse button — for
176
+ displaying files that were already submitted and can no longer be changed. `onFilesAdded`/
177
+ `onFileRemove` aren't called in this mode since there's nothing to add or remove.
178
+
179
+ ---
180
+
181
+ ## 8. Keyboard Navigation of the File List
182
+
183
+ Once files are selected, their chips form a single roving-tabindex group, not one tab stop per
184
+ chip: `Tab` lands on the first chip's remove icon, `Enter`/`Space` removes the focused chip, and
185
+ `ArrowLeft`/`ArrowRight`/`ArrowUp`/`ArrowDown` move focus between chips (wrapping at both ends).
186
+ This is a group-level pattern, not an option on `Chip` used standalone elsewhere. (Not applicable
187
+ in `readOnly` mode, which has no remove icons to navigate to.)
188
+
189
+ ---
190
+
191
+ ## 9. Design System Integration
192
+
193
+ All Recursica components in the `@recursica/adapter-mantine-v8` package adhere strictly to design system spacing, scaling, and behavior patterns.
194
+
195
+ > [!IMPORTANT]
196
+ >
197
+ > - **Anti-override protection**: Rogue style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
198
+ > - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
199
+ > - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in `recursica_variables_scoped.css` and mapped in the component's CSS module.
@@ -0,0 +1,4 @@
1
+ # Flex Implementation Notes
2
+
3
+ The `Flex` component is a generic unopinionated flex layout wrapper mapped directly to Mantine's `Flex`.
4
+ It currently does not require any custom logical layouts or CSS workarounds since it serves only to provide absolute, raw manipulation of standard CSS flex properties. All spacing props (gap, align, justify, direction, wrap) pass safely through via the `filterStylingProps` layout-property allowance, with `rec-` dimension tokens scaling transparently mapped to standard gap limits.
@@ -0,0 +1,7 @@
1
+ /* HARDCODED VALUES:
2
+ * None. This is a generic flex layout wrapper.
3
+ */
4
+
5
+ .root {
6
+ /* Mantine handles flex, gap, align, justify. No intrinsic design-system styles required for pure layout wrappers. */
7
+ }
@@ -0,0 +1,128 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Flex } from "./Flex";
4
+ import { Button } from "../Button";
5
+ import { Text } from "../Text/Text";
6
+
7
+ type FlexStoryProps = React.ComponentProps<typeof Flex>;
8
+
9
+ const meta: Meta<FlexStoryProps> = {
10
+ title: "UI-Kit/Flex",
11
+ component: Flex,
12
+ tags: ["autodocs"],
13
+ parameters: {
14
+ docs: {
15
+ description: {
16
+ component:
17
+ "Flex is a bare-metal flex container that maps directly to Mantine's Flex component, providing unopinionated control over direction, alignment, and wrapping.",
18
+ },
19
+ },
20
+ },
21
+ args: {
22
+ gap: "rec-default",
23
+ align: "center",
24
+ justify: "flex-start",
25
+ direction: "row",
26
+ wrap: "wrap",
27
+ },
28
+ argTypes: {
29
+ gap: {
30
+ control: "select",
31
+ options: [
32
+ "rec-none",
33
+ "rec-sm",
34
+ "rec-default",
35
+ "rec-md",
36
+ "rec-lg",
37
+ "rec-xl",
38
+ "rec-2xl",
39
+ "xs",
40
+ "sm",
41
+ "md",
42
+ "lg",
43
+ "xl",
44
+ ],
45
+ description: "Gap between elements",
46
+ },
47
+ direction: {
48
+ control: "select",
49
+ options: ["row", "column", "row-reverse", "column-reverse"],
50
+ description: "Flex-direction property",
51
+ },
52
+ align: {
53
+ control: "select",
54
+ options: ["flex-start", "center", "flex-end", "stretch"],
55
+ description: "Align-items property",
56
+ },
57
+ justify: {
58
+ control: "select",
59
+ options: [
60
+ "flex-start",
61
+ "center",
62
+ "flex-end",
63
+ "space-between",
64
+ "space-around",
65
+ ],
66
+ description: "Justify-content property",
67
+ },
68
+ wrap: {
69
+ control: "select",
70
+ options: ["wrap", "nowrap", "wrap-reverse"],
71
+ description: "Flex-wrap property",
72
+ },
73
+ defaultChecked: {
74
+ table: { disable: true },
75
+ },
76
+ rowGap: {
77
+ table: { disable: true },
78
+ },
79
+ columnGap: {
80
+ table: { disable: true },
81
+ },
82
+ },
83
+ };
84
+
85
+ export default meta;
86
+
87
+ type Story = StoryObj<FlexStoryProps>;
88
+
89
+ export const Default: Story = {
90
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
91
+ render: ({ withLayer, layer, ...args }: any) => (
92
+ <Flex {...args}>
93
+ <Button variant="solid">Block A</Button>
94
+ <Button variant="outline">Block B</Button>
95
+ <Text>Text inside Flex</Text>
96
+ </Flex>
97
+ ),
98
+ };
99
+
100
+ export const StaticGapSmallColumn: Story = {
101
+ args: {
102
+ gap: "rec-sm",
103
+ direction: "column",
104
+ },
105
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
106
+ render: ({ withLayer, layer, ...args }: any) => (
107
+ <Flex {...args}>
108
+ <Button variant="solid">Item 1</Button>
109
+ <Button variant="solid">Item 2</Button>
110
+ <Button variant="solid">Item 3</Button>
111
+ </Flex>
112
+ ),
113
+ };
114
+
115
+ export const StaticGapLargeRow: Story = {
116
+ args: {
117
+ gap: "rec-xl",
118
+ direction: "row",
119
+ },
120
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
121
+ render: ({ withLayer, layer, ...args }: any) => (
122
+ <Flex {...args}>
123
+ <Button variant="solid">Item 1</Button>
124
+ <Button variant="solid">Item 2</Button>
125
+ <Button variant="solid">Item 3</Button>
126
+ </Flex>
127
+ ),
128
+ };