@pihanga2/shadcn 0.1.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 (399) hide show
  1. package/README.md +114 -0
  2. package/cards/badge/badge.component.d.ts +14 -0
  3. package/cards/badge/badge.component.js +18 -0
  4. package/cards/badge/badge.component.js.map +1 -0
  5. package/cards/badge/badge.types.d.ts +37 -0
  6. package/cards/badge/badge.types.js +7 -0
  7. package/cards/badge/badge.types.js.map +1 -0
  8. package/cards/badge/index.d.ts +1 -0
  9. package/cards/badge/index.js +12 -0
  10. package/cards/badge/index.js.map +1 -0
  11. package/cards/box/box.component.d.ts +4 -0
  12. package/cards/box/box.component.js +24 -0
  13. package/cards/box/box.component.js.map +1 -0
  14. package/cards/box/box.types.d.ts +71 -0
  15. package/cards/box/box.types.js +7 -0
  16. package/cards/box/box.types.js.map +1 -0
  17. package/cards/box/index.d.ts +1 -0
  18. package/cards/box/index.js +12 -0
  19. package/cards/box/index.js.map +1 -0
  20. package/cards/button/button.component.d.ts +4 -0
  21. package/cards/button/button.component.js +71 -0
  22. package/cards/button/button.component.js.map +1 -0
  23. package/cards/button/button.types.d.ts +106 -0
  24. package/cards/button/button.types.js +7 -0
  25. package/cards/button/button.types.js.map +1 -0
  26. package/cards/button/index.d.ts +1 -0
  27. package/cards/button/index.js +13 -0
  28. package/cards/button/index.js.map +1 -0
  29. package/cards/checkbox/checkbox.component.d.ts +4 -0
  30. package/cards/checkbox/checkbox.component.js +36 -0
  31. package/cards/checkbox/checkbox.component.js.map +1 -0
  32. package/cards/checkbox/checkbox.types.d.ts +37 -0
  33. package/cards/checkbox/checkbox.types.js +7 -0
  34. package/cards/checkbox/checkbox.types.js.map +1 -0
  35. package/cards/checkbox/index.d.ts +1 -0
  36. package/cards/checkbox/index.js +13 -0
  37. package/cards/checkbox/index.js.map +1 -0
  38. package/cards/conditional/conditional.component.d.ts +11 -0
  39. package/cards/conditional/conditional.component.js +15 -0
  40. package/cards/conditional/conditional.component.js.map +1 -0
  41. package/cards/conditional/conditional.types.d.ts +29 -0
  42. package/cards/conditional/conditional.types.js +7 -0
  43. package/cards/conditional/conditional.types.js.map +1 -0
  44. package/cards/conditional/index.d.ts +1 -0
  45. package/cards/conditional/index.js +12 -0
  46. package/cards/conditional/index.js.map +1 -0
  47. package/cards/core-index.d.ts +30 -0
  48. package/cards/core-index.js +61 -0
  49. package/cards/dataTable/dataTable.component.d.ts +4 -0
  50. package/cards/dataTable/dataTable.component.js +209 -0
  51. package/cards/dataTable/dataTable.component.js.map +1 -0
  52. package/cards/dataTable/dataTable.types.d.ts +196 -0
  53. package/cards/dataTable/dataTable.types.js +13 -0
  54. package/cards/dataTable/dataTable.types.js.map +1 -0
  55. package/cards/dataTable/dataTableRowDetail.component.d.ts +26 -0
  56. package/cards/dataTable/dataTableRowDetail.component.js +47 -0
  57. package/cards/dataTable/dataTableRowDetail.component.js.map +1 -0
  58. package/cards/dataTable/index.d.ts +1 -0
  59. package/cards/dataTable/index.js +16 -0
  60. package/cards/dataTable/index.js.map +1 -0
  61. package/cards/dialog/dialog.component.d.ts +4 -0
  62. package/cards/dialog/dialog.component.js +89 -0
  63. package/cards/dialog/dialog.component.js.map +1 -0
  64. package/cards/dialog/dialog.types.d.ts +115 -0
  65. package/cards/dialog/dialog.types.js +11 -0
  66. package/cards/dialog/dialog.types.js.map +1 -0
  67. package/cards/dialog/index.d.ts +1 -0
  68. package/cards/dialog/index.js +13 -0
  69. package/cards/dialog/index.js.map +1 -0
  70. package/cards/dropDownMenu/drop-down.component.d.ts +4 -0
  71. package/cards/dropDownMenu/drop-down.component.js +205 -0
  72. package/cards/dropDownMenu/drop-down.component.js.map +1 -0
  73. package/cards/dropDownMenu/drop-down.types.d.ts +131 -0
  74. package/cards/dropDownMenu/drop-down.types.js +7 -0
  75. package/cards/dropDownMenu/drop-down.types.js.map +1 -0
  76. package/cards/dropDownMenu/dropdown-context.d.ts +7 -0
  77. package/cards/dropDownMenu/dropdown-context.js +7 -0
  78. package/cards/dropDownMenu/dropdown-context.js.map +1 -0
  79. package/cards/dropDownMenu/dropdown-menu.ui.d.ts +42 -0
  80. package/cards/dropDownMenu/dropdown-menu.ui.js +111 -0
  81. package/cards/dropDownMenu/dropdown-menu.ui.js.map +1 -0
  82. package/cards/dropDownMenu/index.d.ts +1 -0
  83. package/cards/dropDownMenu/index.js +13 -0
  84. package/cards/dropDownMenu/index.js.map +1 -0
  85. package/cards/emptyCard.d.ts +7 -0
  86. package/cards/field/field.component.d.ts +4 -0
  87. package/cards/field/field.component.js +38 -0
  88. package/cards/field/field.component.js.map +1 -0
  89. package/cards/field/field.types.d.ts +30 -0
  90. package/cards/field/field.types.js +7 -0
  91. package/cards/field/field.types.js.map +1 -0
  92. package/cards/field/index.d.ts +1 -0
  93. package/cards/field/index.js +12 -0
  94. package/cards/field/index.js.map +1 -0
  95. package/cards/flexGrid/flexGrid.component.d.ts +4 -0
  96. package/cards/flexGrid/flexGrid.component.js +40 -0
  97. package/cards/flexGrid/flexGrid.component.js.map +1 -0
  98. package/cards/flexGrid/flexGrid.types.d.ts +23 -0
  99. package/cards/flexGrid/flexGrid.types.js +7 -0
  100. package/cards/flexGrid/flexGrid.types.js.map +1 -0
  101. package/cards/flexGrid/index.d.ts +1 -0
  102. package/cards/flexGrid/index.js +12 -0
  103. package/cards/flexGrid/index.js.map +1 -0
  104. package/cards/form/form.component.d.ts +4 -0
  105. package/cards/form/form.component.js +64 -0
  106. package/cards/form/form.component.js.map +1 -0
  107. package/cards/form/form.context.d.ts +23 -0
  108. package/cards/form/form.context.js +13 -0
  109. package/cards/form/form.context.js.map +1 -0
  110. package/cards/form/form.types.d.ts +43 -0
  111. package/cards/form/form.types.js +7 -0
  112. package/cards/form/form.types.js.map +1 -0
  113. package/cards/form/index.d.ts +2 -0
  114. package/cards/form/index.js +14 -0
  115. package/cards/form/index.js.map +1 -0
  116. package/cards/framework/framework.component.d.ts +3 -0
  117. package/cards/framework/framework.component.js +21 -0
  118. package/cards/framework/framework.component.js.map +1 -0
  119. package/cards/framework/index.d.ts +2 -0
  120. package/cards/framework/index.js +12 -0
  121. package/cards/framework/index.js.map +1 -0
  122. package/cards/graphin/graphin.component.d.ts +4 -0
  123. package/cards/graphin/graphin.types.d.ts +18 -0
  124. package/cards/graphin/index.d.ts +2 -0
  125. package/cards/graphin/tooltip.component.d.ts +10 -0
  126. package/cards/icons.d.ts +15 -0
  127. package/cards/icons.js +15 -0
  128. package/cards/icons.js.map +1 -0
  129. package/cards/input/index.d.ts +1 -0
  130. package/cards/input/index.js +13 -0
  131. package/cards/input/index.js.map +1 -0
  132. package/cards/input/input.component.d.ts +4 -0
  133. package/cards/input/input.component.js +69 -0
  134. package/cards/input/input.component.js.map +1 -0
  135. package/cards/input/input.types.d.ts +80 -0
  136. package/cards/input/input.types.js +7 -0
  137. package/cards/input/input.types.js.map +1 -0
  138. package/cards/jsonViewer/index.d.ts +1 -0
  139. package/cards/jsonViewer/jsonViewer.component.d.ts +4 -0
  140. package/cards/jsonViewer/jsonViewer.types.d.ts +76 -0
  141. package/cards/list/index.d.ts +1 -0
  142. package/cards/list/index.js +13 -0
  143. package/cards/list/index.js.map +1 -0
  144. package/cards/list/list.component.d.ts +4 -0
  145. package/cards/list/list.component.js +102 -0
  146. package/cards/list/list.component.js.map +1 -0
  147. package/cards/list/list.types.d.ts +65 -0
  148. package/cards/list/list.types.js +7 -0
  149. package/cards/list/list.types.js.map +1 -0
  150. package/cards/loadingOverlay/index.d.ts +1 -0
  151. package/cards/loadingOverlay/index.js +12 -0
  152. package/cards/loadingOverlay/index.js.map +1 -0
  153. package/cards/loadingOverlay/loadingOverlay.component.d.ts +4 -0
  154. package/cards/loadingOverlay/loadingOverlay.component.js +31 -0
  155. package/cards/loadingOverlay/loadingOverlay.component.js.map +1 -0
  156. package/cards/loadingOverlay/loadingOverlay.types.d.ts +13 -0
  157. package/cards/loadingOverlay/loadingOverlay.types.js +7 -0
  158. package/cards/loadingOverlay/loadingOverlay.types.js.map +1 -0
  159. package/cards/loadingSkeleton/index.d.ts +1 -0
  160. package/cards/loadingSkeleton/index.js +12 -0
  161. package/cards/loadingSkeleton/index.js.map +1 -0
  162. package/cards/loadingSkeleton/loading-skeleton.component.d.ts +19 -0
  163. package/cards/loadingSkeleton/loading-skeleton.component.js +33 -0
  164. package/cards/loadingSkeleton/loading-skeleton.component.js.map +1 -0
  165. package/cards/loadingSkeleton/loading-skeleton.types.d.ts +94 -0
  166. package/cards/loadingSkeleton/loading-skeleton.types.js +7 -0
  167. package/cards/loadingSkeleton/loading-skeleton.types.js.map +1 -0
  168. package/cards/markdownViewer/index.d.ts +1 -0
  169. package/cards/markdownViewer/markdownViewer.component.d.ts +4 -0
  170. package/cards/markdownViewer/markdownViewer.types.d.ts +28 -0
  171. package/cards/menu/index.d.ts +1 -0
  172. package/cards/menu/index.js +13 -0
  173. package/cards/menu/index.js.map +1 -0
  174. package/cards/menu/menu.component.d.ts +4 -0
  175. package/cards/menu/menu.component.js +23 -0
  176. package/cards/menu/menu.component.js.map +1 -0
  177. package/cards/menu/menu.types.d.ts +53 -0
  178. package/cards/menu/menu.types.js +7 -0
  179. package/cards/menu/menu.types.js.map +1 -0
  180. package/cards/modeToggle/index.d.ts +1 -0
  181. package/cards/modeToggle/index.js +13 -0
  182. package/cards/modeToggle/index.js.map +1 -0
  183. package/cards/modeToggle/mode-toggle.component.d.ts +4 -0
  184. package/cards/modeToggle/mode-toggle.component.js +32 -0
  185. package/cards/modeToggle/mode-toggle.component.js.map +1 -0
  186. package/cards/modeToggle/mode-toggle.types.d.ts +20 -0
  187. package/cards/modeToggle/mode-toggle.types.js +7 -0
  188. package/cards/modeToggle/mode-toggle.types.js.map +1 -0
  189. package/cards/navbarSearch/index.d.ts +1 -0
  190. package/cards/navbarSearch/index.js +13 -0
  191. package/cards/navbarSearch/index.js.map +1 -0
  192. package/cards/navbarSearch/navbarSearch.component.d.ts +3 -0
  193. package/cards/navbarSearch/navbarSearch.component.js +88 -0
  194. package/cards/navbarSearch/navbarSearch.component.js.map +1 -0
  195. package/cards/navbarSearch/navbarSearch.type.d.ts +32 -0
  196. package/cards/navbarSearch/navbarSearch.type.js +11 -0
  197. package/cards/navbarSearch/navbarSearch.type.js.map +1 -0
  198. package/cards/pageWithNavbar/index.d.ts +2 -0
  199. package/cards/pageWithNavbar/index.js +14 -0
  200. package/cards/pageWithNavbar/index.js.map +1 -0
  201. package/cards/pageWithNavbar/pageWithNavbar.component.d.ts +12 -0
  202. package/cards/pageWithNavbar/pageWithNavbar.component.js +153 -0
  203. package/cards/pageWithNavbar/pageWithNavbar.component.js.map +1 -0
  204. package/cards/pageWithNavbar/pageWithNavbar.type.d.ts +81 -0
  205. package/cards/pageWithNavbar/pageWithNavbar.type.js +7 -0
  206. package/cards/pageWithNavbar/pageWithNavbar.type.js.map +1 -0
  207. package/cards/pasteTarget/index.d.ts +1 -0
  208. package/cards/pasteTarget/index.js +13 -0
  209. package/cards/pasteTarget/index.js.map +1 -0
  210. package/cards/pasteTarget/pasteTarget.component.d.ts +4 -0
  211. package/cards/pasteTarget/pasteTarget.component.js +158 -0
  212. package/cards/pasteTarget/pasteTarget.component.js.map +1 -0
  213. package/cards/pasteTarget/pasteTarget.types.d.ts +46 -0
  214. package/cards/pasteTarget/pasteTarget.types.js +11 -0
  215. package/cards/pasteTarget/pasteTarget.types.js.map +1 -0
  216. package/cards/resizable/index.d.ts +1 -0
  217. package/cards/resizable/resizable.component.d.ts +4 -0
  218. package/cards/resizable/resizable.types.d.ts +49 -0
  219. package/cards/select/index.d.ts +1 -0
  220. package/cards/select/index.js +13 -0
  221. package/cards/select/index.js.map +1 -0
  222. package/cards/select/select.component.d.ts +4 -0
  223. package/cards/select/select.component.js +41 -0
  224. package/cards/select/select.component.js.map +1 -0
  225. package/cards/select/select.types.d.ts +73 -0
  226. package/cards/select/select.types.js +11 -0
  227. package/cards/select/select.types.js.map +1 -0
  228. package/cards/stack/index.d.ts +1 -0
  229. package/cards/stack/index.js +12 -0
  230. package/cards/stack/index.js.map +1 -0
  231. package/cards/stack/stack.component.d.ts +7 -0
  232. package/cards/stack/stack.component.js +83 -0
  233. package/cards/stack/stack.component.js.map +1 -0
  234. package/cards/stack/stack.types.d.ts +47 -0
  235. package/cards/stack/stack.types.js +7 -0
  236. package/cards/stack/stack.types.js.map +1 -0
  237. package/cards/stepper/index.d.ts +1 -0
  238. package/cards/stepper/index.js +13 -0
  239. package/cards/stepper/index.js.map +1 -0
  240. package/cards/stepper/stepper.component.d.ts +4 -0
  241. package/cards/stepper/stepper.component.js +48 -0
  242. package/cards/stepper/stepper.component.js.map +1 -0
  243. package/cards/stepper/stepper.types.d.ts +96 -0
  244. package/cards/stepper/stepper.types.js +7 -0
  245. package/cards/stepper/stepper.types.js.map +1 -0
  246. package/cards/switch/index.d.ts +1 -0
  247. package/cards/switch/index.js +13 -0
  248. package/cards/switch/index.js.map +1 -0
  249. package/cards/switch/switch.component.d.ts +4 -0
  250. package/cards/switch/switch.component.js +34 -0
  251. package/cards/switch/switch.component.js.map +1 -0
  252. package/cards/switch/switch.types.d.ts +39 -0
  253. package/cards/switch/switch.types.js +7 -0
  254. package/cards/switch/switch.types.js.map +1 -0
  255. package/cards/tabs/index.d.ts +1 -0
  256. package/cards/tabs/index.js +13 -0
  257. package/cards/tabs/index.js.map +1 -0
  258. package/cards/tabs/tabs.component.d.ts +4 -0
  259. package/cards/tabs/tabs.component.js +42 -0
  260. package/cards/tabs/tabs.component.js.map +1 -0
  261. package/cards/tabs/tabs.types.d.ts +72 -0
  262. package/cards/tabs/tabs.types.js +7 -0
  263. package/cards/tabs/tabs.types.js.map +1 -0
  264. package/cards/textField/index.d.ts +1 -0
  265. package/cards/textField/index.js +13 -0
  266. package/cards/textField/index.js.map +1 -0
  267. package/cards/textField/textField.component.d.ts +4 -0
  268. package/cards/textField/textField.component.js +29 -0
  269. package/cards/textField/textField.component.js.map +1 -0
  270. package/cards/textField/textField.types.d.ts +36 -0
  271. package/cards/textField/textField.types.js +7 -0
  272. package/cards/textField/textField.types.js.map +1 -0
  273. package/cards/toast/index.d.ts +1 -0
  274. package/cards/toast/index.js +13 -0
  275. package/cards/toast/index.js.map +1 -0
  276. package/cards/toast/toast.component.d.ts +4 -0
  277. package/cards/toast/toast.component.js +76 -0
  278. package/cards/toast/toast.component.js.map +1 -0
  279. package/cards/toast/toast.types.d.ts +100 -0
  280. package/cards/toast/toast.types.js +12 -0
  281. package/cards/toast/toast.types.js.map +1 -0
  282. package/cards/toggleGroup/index.d.ts +1 -0
  283. package/cards/toggleGroup/index.js +13 -0
  284. package/cards/toggleGroup/index.js.map +1 -0
  285. package/cards/toggleGroup/toggleGroup.component.d.ts +4 -0
  286. package/cards/toggleGroup/toggleGroup.component.js +55 -0
  287. package/cards/toggleGroup/toggleGroup.component.js.map +1 -0
  288. package/cards/toggleGroup/toggleGroup.types.d.ts +96 -0
  289. package/cards/toggleGroup/toggleGroup.types.js +7 -0
  290. package/cards/toggleGroup/toggleGroup.types.js.map +1 -0
  291. package/cards/types.d.ts +72 -0
  292. package/cards/types.js +22 -0
  293. package/cards/types.js.map +1 -0
  294. package/cards/typography/index.d.ts +1 -0
  295. package/cards/typography/index.js +12 -0
  296. package/cards/typography/index.js.map +1 -0
  297. package/cards/typography/typography.component.d.ts +4 -0
  298. package/cards/typography/typography.component.js +44 -0
  299. package/cards/typography/typography.component.js.map +1 -0
  300. package/cards/typography/typography.types.d.ts +23 -0
  301. package/cards/typography/typography.types.js +7 -0
  302. package/cards/typography/typography.types.js.map +1 -0
  303. package/components/hooks/use-is-touch-device.d.ts +1 -0
  304. package/components/lib/utils.js +10 -0
  305. package/components/lib/utils.js.map +1 -0
  306. package/components/theme-provider/index.d.ts +4 -0
  307. package/components/theme-provider/index.js +12 -0
  308. package/components/theme-provider/index.js.map +1 -0
  309. package/components/theme-provider/theme-provider.component.d.ts +2 -0
  310. package/components/theme-provider/theme-provider.component.js +31 -0
  311. package/components/theme-provider/theme-provider.component.js.map +1 -0
  312. package/components/theme-provider/theme-provider.types.d.ts +12 -0
  313. package/components/theme-provider/theme-provider.types.js +9 -0
  314. package/components/theme-provider/theme-provider.types.js.map +1 -0
  315. package/components/ui/avatar.js +30 -0
  316. package/components/ui/avatar.js.map +1 -0
  317. package/components/ui/badge.js +26 -0
  318. package/components/ui/badge.js.map +1 -0
  319. package/components/ui/button.js +98 -0
  320. package/components/ui/button.js.map +1 -0
  321. package/components/ui/dialog.js +109 -0
  322. package/components/ui/dialog.js.map +1 -0
  323. package/components/ui/field.js +68 -0
  324. package/components/ui/field.js.map +1 -0
  325. package/components/ui/input.js +16 -0
  326. package/components/ui/input.js.map +1 -0
  327. package/components/ui/label.js +16 -0
  328. package/components/ui/label.js.map +1 -0
  329. package/components/ui/menubar.js +62 -0
  330. package/components/ui/menubar.js.map +1 -0
  331. package/components/ui/select.js +84 -0
  332. package/components/ui/select.js.map +1 -0
  333. package/components/ui/separator.js +5 -0
  334. package/components/ui/sheet.js +49 -0
  335. package/components/ui/sheet.js.map +1 -0
  336. package/components/ui/spinner.js +35 -0
  337. package/components/ui/spinner.js.map +1 -0
  338. package/components/ui/stepper.js +145 -0
  339. package/components/ui/stepper.js.map +1 -0
  340. package/components/ui/switch.js +20 -0
  341. package/components/ui/switch.js.map +1 -0
  342. package/components/ui/table.js +61 -0
  343. package/components/ui/table.js.map +1 -0
  344. package/components/ui/tabs.js +38 -0
  345. package/components/ui/tabs.js.map +1 -0
  346. package/components/ui/toggle-group.js +50 -0
  347. package/components/ui/toggle-group.js.map +1 -0
  348. package/components/ui/toggle.js +27 -0
  349. package/components/ui/toggle.js.map +1 -0
  350. package/components/ui/tooltip.js +38 -0
  351. package/components/ui/tooltip.js.map +1 -0
  352. package/lib/utils.d.ts +2 -0
  353. package/lib/utils.js +10 -0
  354. package/lib/utils.js.map +1 -0
  355. package/mountain-snow.svg +4 -0
  356. package/package.json +203 -0
  357. package/pihanga-shadcn.css +2 -0
  358. package/r/badge.json +35 -0
  359. package/r/box.json +26 -0
  360. package/r/button.json +39 -0
  361. package/r/checkbox.json +31 -0
  362. package/r/conditional.json +30 -0
  363. package/r/dataTable.json +43 -0
  364. package/r/dialog.json +36 -0
  365. package/r/dropDownMenu.json +47 -0
  366. package/r/field.json +35 -0
  367. package/r/flexGrid.json +32 -0
  368. package/r/form.json +37 -0
  369. package/r/framework.json +28 -0
  370. package/r/graphin.json +42 -0
  371. package/r/input.json +35 -0
  372. package/r/jsonViewer.json +40 -0
  373. package/r/list.json +39 -0
  374. package/r/loadingOverlay.json +39 -0
  375. package/r/loadingSkeleton.json +30 -0
  376. package/r/markdownViewer.json +51 -0
  377. package/r/menu.json +34 -0
  378. package/r/modeToggle.json +36 -0
  379. package/r/navbarSearch.json +31 -0
  380. package/r/pageWithNavbar.json +46 -0
  381. package/r/pasteTarget.json +41 -0
  382. package/r/pihanga-base.json +11 -0
  383. package/r/pihanga-cards-icons.json +16 -0
  384. package/r/pihanga-cards-types.json +16 -0
  385. package/r/pihanga-hook-use-is-touch-device.json +15 -0
  386. package/r/pihanga-lib-utils.json +18 -0
  387. package/r/pihanga-theme-provider.json +25 -0
  388. package/r/pihanga-ui-extras.json +68 -0
  389. package/r/registry.json +171 -0
  390. package/r/resizable.json +35 -0
  391. package/r/select.json +35 -0
  392. package/r/stack.json +27 -0
  393. package/r/stepper.json +39 -0
  394. package/r/switch.json +36 -0
  395. package/r/tabs.json +33 -0
  396. package/r/textField.json +32 -0
  397. package/r/toast.json +32 -0
  398. package/r/toggleGroup.json +35 -0
  399. package/r/typography.json +31 -0
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "loadingOverlay",
4
+ "type": "registry:component",
5
+ "description": "Pihanga loadingOverlay card component",
6
+ "dependencies": [
7
+ "clsx@^2.1.1"
8
+ ],
9
+ "registryDependencies": [
10
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
11
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-ui-extras.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "cards/loadingOverlay/index.ts",
16
+ "content": "import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {LoadingOverlayComponent} from \"./loadingOverlay.component\";\nimport {LOADING_OVERLAY_CARD} from \"./loadingOverlay.types\";\n\nexport * from \"./loadingOverlay.types\";\n\nregisterCardComponent({\n name: LOADING_OVERLAY_CARD,\n component: LoadingOverlayComponent,\n});\n",
17
+ "type": "registry:component",
18
+ "target": "src/cards/loadingOverlay/index.ts"
19
+ },
20
+ {
21
+ "path": "cards/loadingOverlay/loadingOverlay.component.tsx",
22
+ "content": "import * as React from \"react\";\nimport clsx from \"clsx\";\n\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\n\nimport {Spinner} from \"@/components/ui/spinner\";\n\nimport type {LoadingOverlayProps} from \"./loadingOverlay.types\";\nimport \"./loadingOverlay.css\";\n\nexport const LoadingOverlayComponent = (\n props: PiCardProps<LoadingOverlayProps>,\n): React.ReactNode => {\n const {\n cardName,\n content,\n isLoading,\n label,\n fillParent,\n viewportCentered,\n className,\n contentClassName,\n overlayClassName,\n } = props;\n\n if (!content) return null;\n\n return (\n <div\n data-pihanga={cardName}\n className={clsx(\n \"overlay-wrapper\",\n isLoading && \"is-loading\",\n fillParent && \"fill-parent\",\n viewportCentered && \"viewport-centered\",\n className,\n )}\n >\n <div className={clsx(\"overlay-content\", contentClassName)}>\n <Card cardName={content} parentCard={cardName} />\n <div className={clsx(\"loading-overlay\", overlayClassName)}>\n <div className=\"loading-overlay__content\">\n <Spinner size=\"icon\" />\n <span>{label ?? \"Loading...\"}</span>\n </div>\n </div>\n </div>\n </div>\n );\n};\n",
23
+ "type": "registry:component",
24
+ "target": "src/cards/loadingOverlay/loadingOverlay.component.tsx"
25
+ },
26
+ {
27
+ "path": "cards/loadingOverlay/loadingOverlay.css",
28
+ "content": ".overlay-wrapper {\n position: relative;\n width: 100%;\n min-width: 0;\n\n display: flex;\n flex-direction: column;\n}\n\n.overlay-wrapper.is-loading.fill-parent {\n min-height: 12rem;\n}\n\n.overlay-content {\n position: relative;\n width: 100%;\n min-width: 0;\n\n display: flex;\n flex-direction: column;\n overflow-y: hidden;\n /* Prevent double scrollbars */\n flex-grow: 1;\n}\n\n.overlay-wrapper.fill-parent .overlay-content {\n min-height: inherit;\n}\n\n.loading-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: oklch(1 0 0 / 0.7);\n opacity: 0;\n pointer-events: none;\n transition: opacity 150ms ease;\n z-index: 2;\n}\n\n.overlay-wrapper.viewport-centered .loading-overlay {\n position: fixed;\n inset: 0;\n z-index: 50;\n}\n\n.dark .loading-overlay {\n background: oklch(0.145 0 0 / 0.5);\n}\n\n.overlay-wrapper.is-loading .loading-overlay {\n opacity: 1;\n pointer-events: auto;\n}\n\n.loading-overlay__content {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.875rem;\n color: var(--muted-foreground);\n}",
29
+ "type": "registry:component",
30
+ "target": "src/cards/loadingOverlay/loadingOverlay.css"
31
+ },
32
+ {
33
+ "path": "cards/loadingOverlay/loadingOverlay.types.ts",
34
+ "content": "import {createCardDeclaration, type PiCardRef} from \"@pihanga2/core\";\n\nexport const LOADING_OVERLAY_CARD = \"loading-overlay\";\n\nexport type LoadingOverlayProps = {\n content: PiCardRef;\n isLoading?: boolean;\n label?: string;\n fillParent?: boolean;\n viewportCentered?: boolean;\n className?: string;\n contentClassName?: string;\n overlayClassName?: string;\n};\n\nexport const LoadingOverlay = createCardDeclaration<LoadingOverlayProps>(\n LOADING_OVERLAY_CARD,\n);\n",
35
+ "type": "registry:component",
36
+ "target": "src/cards/loadingOverlay/loadingOverlay.types.ts"
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "loadingSkeleton",
4
+ "type": "registry:component",
5
+ "description": "Pihanga loadingSkeleton card component",
6
+ "dependencies": [],
7
+ "registryDependencies": [
8
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "cards/loadingSkeleton/index.ts",
13
+ "content": "import {registerCardComponent} from \"@pihanga2/core\";\nimport {LoadingSkeletonComponent} from \"./loading-skeleton.component\";\nimport {LOADING_SKELETON_CARD} from \"./loading-skeleton.types\";\n\nexport * from \"./loading-skeleton.types\";\n\n// No events — display-only card.\nregisterCardComponent({\n name: LOADING_SKELETON_CARD,\n component: LoadingSkeletonComponent,\n});\n",
14
+ "type": "registry:component",
15
+ "target": "src/cards/loadingSkeleton/index.ts"
16
+ },
17
+ {
18
+ "path": "cards/loadingSkeleton/loading-skeleton.component.tsx",
19
+ "content": "import * as React from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport type {\n LoadingSkeletonProps,\n SkeletonRowSize,\n SkeletonSpacing,\n} from \"./loading-skeleton.types\";\n\n// ── Preset maps ───────────────────────────────────────────────────────────────\n\n/** Height class for each named `rowSize` preset. */\nconst ROW_SIZE_CLASS: Record<SkeletonRowSize, string> = {\n xs: \"h-3\", // 12 px — tiny text / timestamp lines\n sm: \"h-5\", // 20 px — small text or menu items\n md: \"h-10\", // 40 px — standard input / row\n lg: \"h-14\", // 56 px — large card rows\n xl: \"h-20\", // 80 px — wide widget / hero section\n};\n\n/** Gap class for each named `spacing` preset. */\nconst SPACING_CLASS: Record<SkeletonSpacing, string> = {\n sm: \"gap-2\", // 8 px\n md: \"gap-3\", // 12 px\n lg: \"gap-4\", // 16 px\n};\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\n/**\n * LoadingSkeletonComponent\n *\n * While `loading` is `true`: renders `rows` animated shimmer rows inside a\n * flex column wrapper.\n *\n * **Presets** — `rowSize` and `spacing` select named height / gap values\n * without needing Tailwind knowledge.\n *\n * **Raw overrides** — `rowClassName` (overrides `rowSize`) and `className`\n * (overrides `spacing`) accept arbitrary Tailwind classes for custom layouts.\n *\n * While `loading` is `false`: transparently passes through to `content` (no\n * extra DOM wrapper) or renders nothing when `content` is omitted.\n */\nexport const LoadingSkeletonComponent = (\n props: PiCardProps<LoadingSkeletonProps>,\n): React.ReactNode => {\n const {\n cardName,\n loading,\n rows = 3,\n rowSize = \"md\",\n spacing = \"md\",\n rowClassName,\n className,\n content,\n } = props;\n\n if (loading) {\n // rawClassName takes precedence over the preset; preset is the fallback.\n const rowCls =\n rowClassName ?? `rounded-md bg-foreground/15 ${ROW_SIZE_CLASS[rowSize]}`;\n const wrapperCls =\n className ?? `flex w-full flex-col ${SPACING_CLASS[spacing]}`;\n\n return (\n <div data-pihanga={cardName} className={wrapperCls}>\n {Array.from({length: rows}).map((_, i) => (\n <div key={i} className={`animate-pulse ${rowCls}`} />\n ))}\n </div>\n );\n }\n\n if (content) {\n // Transparent pass-through: no extra wrapper element.\n return <Card cardName={content} parentCard={cardName} />;\n }\n\n // Loaded with no content card — render nothing.\n return null;\n};\n",
20
+ "type": "registry:component",
21
+ "target": "src/cards/loadingSkeleton/loading-skeleton.component.tsx"
22
+ },
23
+ {
24
+ "path": "cards/loadingSkeleton/loading-skeleton.types.ts",
25
+ "content": "import {createCardDeclaration} from \"@pihanga2/core\";\nimport type {PiCardRef} from \"@pihanga2/core\";\n\n// ── Card id ───────────────────────────────────────────────────────────────────\n\nexport const LOADING_SKELETON_CARD = \"shad/loading-skeleton\";\n\n// ── Card declaration factory ──────────────────────────────────────────────────\n\nexport const LoadingSkeleton = createCardDeclaration<LoadingSkeletonProps>(\n LOADING_SKELETON_CARD,\n);\n\n// ── Preset enums ──────────────────────────────────────────────────────────────\n\n/**\n * Named height presets for skeleton rows.\n *\n * | Value | Height | Typical use |\n * |-------|----------|---------------------------------|\n * | `xs` | 12 px | Tiny text / timestamp lines |\n * | `sm` | 20 px | Small text or menu items |\n * | `md` | 40 px | Standard input / row (default) |\n * | `lg` | 56 px | Large card rows |\n * | `xl` | 80 px | Wide widget / hero section |\n */\nexport type SkeletonRowSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\n/**\n * Named gap presets between skeleton rows.\n *\n * | Value | Gap |\n * |-------|--------|\n * | `sm` | 8 px |\n * | `md` | 12 px |\n * | `lg` | 16 px |\n */\nexport type SkeletonSpacing = \"sm\" | \"md\" | \"lg\";\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\n/**\n * Props for the `shad/loading-skeleton` card.\n *\n * Shows animated shimmer rows while `loading` is `true`; once `loading`\n * becomes `false` it transparently passes through to `content` (or renders\n * nothing when `content` is omitted).\n *\n * **Quick setup** — use the preset props:\n * ```ts\n * registerCard(\"myApp/area\", LoadingSkeleton({\n * loading: memo((s: AppState) => s.dataLoading),\n * rows: 4,\n * rowSize: \"lg\",\n * spacing: \"lg\",\n * content: \"myApp/dataList\",\n * }));\n * ```\n *\n * **Custom Tailwind** — override with raw class strings:\n * ```ts\n * registerCard(\"myApp/area\", LoadingSkeleton({\n * loading: memo((s: AppState) => s.dataLoading),\n * rows: 4,\n * rowClassName: \"h-32 rounded-xl bg-primary/10\",\n * className: \"grid grid-cols-2 gap-4 w-full\",\n * content: \"myApp/cardGrid\",\n * }));\n * ```\n */\nexport type LoadingSkeletonProps = {\n /** When `true`, render animated skeleton rows; when `false` render\n * `content` (or nothing if `content` is omitted). */\n loading: boolean;\n\n /**\n * Number of placeholder rows to render while loading.\n * @default 3\n */\n rows?: number;\n\n // ── Preset controls (no Tailwind knowledge needed) ────────────────────────\n\n /**\n * Named height preset for each shimmer row.\n * Ignored when `rowClassName` is provided.\n * @default \"md\"\n */\n rowSize?: SkeletonRowSize;\n\n /**\n * Named gap preset between shimmer rows.\n * Ignored when `className` is provided.\n * @default \"md\"\n */\n spacing?: SkeletonSpacing;\n\n // ── Raw Tailwind overrides (power users) ──────────────────────────────────\n\n /**\n * Raw Tailwind classes for every shimmer row.\n * When set, takes precedence over `rowSize`.\n * Must include a height (`h-*`) and optionally `rounded-*` and `bg-*`.\n * Tip: use `bg-foreground/15` for good contrast in both themes.\n */\n rowClassName?: string;\n\n /**\n * Raw Tailwind classes for the outer `<div>` wrapper (loading state only).\n * When set, takes precedence over `spacing`.\n * Include `w-full` to ensure rows fill the container.\n */\n className?: string;\n\n /**\n * Card to render when `loading` is `false`.\n * When omitted the card renders nothing in the loaded state.\n */\n content?: PiCardRef;\n};\n// No events — display-only card.\n",
26
+ "type": "registry:component",
27
+ "target": "src/cards/loadingSkeleton/loading-skeleton.types.ts"
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "markdownViewer",
4
+ "type": "registry:component",
5
+ "description": "Pihanga markdownViewer card component",
6
+ "dependencies": [
7
+ "react-markdown@UNKNOWN - add to root package.json",
8
+ "rehype-highlight@^7.0.2",
9
+ "rehype-katex@UNKNOWN - add to root package.json",
10
+ "mermaid@^11.15.0",
11
+ "remark-gfm@UNKNOWN - add to root package.json",
12
+ "remark-math@UNKNOWN - add to root package.json",
13
+ "unified@UNKNOWN - add to root package.json",
14
+ "highlight.js@^11.11.1"
15
+ ],
16
+ "registryDependencies": [
17
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json"
18
+ ],
19
+ "files": [
20
+ {
21
+ "path": "cards/markdownViewer/index.ts",
22
+ "content": "import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {MarkdownViewerComponent} from \"./markdownViewer.component\";\nimport {MARKDOWN_CARD} from \"./markdownViewer.types\";\n\nexport * from \"./markdownViewer.types\";\n\nregisterCardComponent({\n name: MARKDOWN_CARD,\n component: MarkdownViewerComponent,\n});\n",
23
+ "type": "registry:component",
24
+ "target": "src/cards/markdownViewer/index.ts"
25
+ },
26
+ {
27
+ "path": "cards/markdownViewer/katex.css",
28
+ "content": "/* stylelint-disable font-family-no-missing-generic-family-keyword */\n@font-face {\n font-family: \"KaTeX_AMS\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_AMS-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_AMS-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_AMS-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Caligraphic\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Bold.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Bold.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Bold.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Caligraphic\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Fraktur\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Bold.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Bold.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Bold.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Fraktur\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Main\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Bold.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Bold.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Bold.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Main\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-BoldItalic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-BoldItalic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-BoldItalic.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_Main\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Italic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Italic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Italic.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_Main\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Math\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-BoldItalic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-BoldItalic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-BoldItalic.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_Math\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-Italic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-Italic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-Italic.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_SansSerif\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Bold.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Bold.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Bold.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_SansSerif\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Italic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Italic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Italic.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_SansSerif\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Script\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Script-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Script-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Script-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Size1\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size1-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size1-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size1-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Size2\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size2-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size2-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size2-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Size3\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size3-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size3-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size3-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Size4\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size4-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size4-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size4-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Typewriter\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Typewriter-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Typewriter-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Typewriter-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n.katex {\n font: normal 1.21em KaTeX_Main, Times New Roman, serif;\n line-height: 1.2;\n text-indent: 0;\n text-rendering: auto;\n}\n\n.katex * {\n -ms-high-contrast-adjust: none !important;\n}\n\n.katex * {\n border-color: currentColor;\n}\n\n.katex .katex-version::after {\n content: \"0.16.11\";\n}\n\n.katex .katex-mathml {\n /* Accessibility hack to only show to screen readers\n Found at: http://a11yproject.com/posts/how-to-hide-content/ */\n position: absolute;\n clip: rect(1px, 1px, 1px, 1px);\n padding: 0;\n border: 0;\n height: 1px;\n width: 1px;\n overflow: hidden;\n}\n\n.katex .katex-html {\n /* \\newline is an empty block at top level, between .base elements */\n}\n\n.katex .katex-html>.newline {\n display: block;\n}\n\n.katex .base {\n position: relative;\n display: inline-block;\n white-space: nowrap;\n width: -webkit-min-content;\n width: -moz-min-content;\n width: min-content;\n}\n\n.katex .strut {\n display: inline-block;\n}\n\n.katex .textbf {\n font-weight: bold;\n}\n\n.katex .textit {\n font-style: italic;\n}\n\n.katex .textrm {\n font-family: KaTeX_Main;\n}\n\n.katex .textsf {\n font-family: KaTeX_SansSerif;\n}\n\n.katex .texttt {\n font-family: KaTeX_Typewriter;\n}\n\n.katex .mathnormal {\n font-family: KaTeX_Math;\n font-style: italic;\n}\n\n.katex .mathit {\n font-family: KaTeX_Main;\n font-style: italic;\n}\n\n.katex .mathrm {\n font-style: normal;\n}\n\n.katex .mathbf {\n font-family: KaTeX_Main;\n font-weight: bold;\n}\n\n.katex .boldsymbol {\n font-family: KaTeX_Math;\n font-weight: bold;\n font-style: italic;\n}\n\n.katex .amsrm {\n font-family: KaTeX_AMS;\n}\n\n.katex .mathbb,\n.katex .textbb {\n font-family: KaTeX_AMS;\n}\n\n.katex .mathcal {\n font-family: KaTeX_Caligraphic;\n}\n\n.katex .mathfrak,\n.katex .textfrak {\n font-family: KaTeX_Fraktur;\n}\n\n.katex .mathboldfrak,\n.katex .textboldfrak {\n font-family: KaTeX_Fraktur;\n font-weight: bold;\n}\n\n.katex .mathtt {\n font-family: KaTeX_Typewriter;\n}\n\n.katex .mathscr,\n.katex .textscr {\n font-family: KaTeX_Script;\n}\n\n.katex .mathsf,\n.katex .textsf {\n font-family: KaTeX_SansSerif;\n}\n\n.katex .mathboldsf,\n.katex .textboldsf {\n font-family: KaTeX_SansSerif;\n font-weight: bold;\n}\n\n.katex .mathitsf,\n.katex .textitsf {\n font-family: KaTeX_SansSerif;\n font-style: italic;\n}\n\n.katex .mainrm {\n font-family: KaTeX_Main;\n font-style: normal;\n}\n\n.katex .vlist-t {\n display: inline-table;\n table-layout: fixed;\n border-collapse: collapse;\n}\n\n.katex .vlist-r {\n display: table-row;\n}\n\n.katex .vlist {\n display: table-cell;\n vertical-align: bottom;\n position: relative;\n}\n\n.katex .vlist>span {\n display: block;\n height: 0;\n position: relative;\n}\n\n.katex .vlist>span>span {\n display: inline-block;\n}\n\n.katex .vlist>span>.pstrut {\n overflow: hidden;\n width: 0;\n}\n\n.katex .vlist-t2 {\n margin-right: -2px;\n}\n\n.katex .vlist-s {\n display: table-cell;\n vertical-align: bottom;\n font-size: 1px;\n width: 2px;\n min-width: 2px;\n}\n\n.katex .vbox {\n display: inline-flex;\n flex-direction: column;\n align-items: baseline;\n}\n\n.katex .hbox {\n display: inline-flex;\n flex-direction: row;\n width: 100%;\n}\n\n.katex .thinbox {\n display: inline-flex;\n flex-direction: row;\n width: 0;\n max-width: 0;\n}\n\n.katex .msupsub {\n text-align: left;\n}\n\n.katex .mfrac>span>span {\n text-align: center;\n}\n\n.katex .mfrac .frac-line {\n display: inline-block;\n width: 100%;\n border-bottom-style: solid;\n}\n\n.katex .mfrac .frac-line,\n.katex .overline .overline-line,\n.katex .underline .underline-line,\n.katex .hline,\n.katex .hdashline,\n.katex .rule {\n min-height: 1px;\n}\n\n.katex .mspace {\n display: inline-block;\n}\n\n.katex .llap,\n.katex .rlap,\n.katex .clap {\n width: 0;\n position: relative;\n}\n\n.katex .llap>.inner,\n.katex .rlap>.inner,\n.katex .clap>.inner {\n position: absolute;\n}\n\n.katex .llap>.fix,\n.katex .rlap>.fix,\n.katex .clap>.fix {\n display: inline-block;\n}\n\n.katex .llap>.inner {\n right: 0;\n}\n\n.katex .rlap>.inner,\n.katex .clap>.inner {\n left: 0;\n}\n\n.katex .clap>.inner>span {\n margin-left: -50%;\n margin-right: 50%;\n}\n\n.katex .rule {\n display: inline-block;\n border: solid 0;\n position: relative;\n}\n\n.katex .overline .overline-line,\n.katex .underline .underline-line,\n.katex .hline {\n display: inline-block;\n width: 100%;\n border-bottom-style: solid;\n}\n\n.katex .hdashline {\n display: inline-block;\n width: 100%;\n border-bottom-style: dashed;\n}\n\n.katex .sqrt>.root {\n /* These values are taken from the definition of `\\r@@t`,\n `\\mkern 5mu` and `\\mkern -10mu`. */\n margin-left: 0.2777777778em;\n margin-right: -0.5555555556em;\n}\n\n.katex .sizing.reset-size1.size1,\n.katex .fontsize-ensurer.reset-size1.size1 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size1.size2,\n.katex .fontsize-ensurer.reset-size1.size2 {\n /* stylelint-disable-next-line */\n font-size: 1.2em;\n}\n\n.katex .sizing.reset-size1.size3,\n.katex .fontsize-ensurer.reset-size1.size3 {\n /* stylelint-disable-next-line */\n font-size: 1.4em;\n}\n\n.katex .sizing.reset-size1.size4,\n.katex .fontsize-ensurer.reset-size1.size4 {\n /* stylelint-disable-next-line */\n font-size: 1.6em;\n}\n\n.katex .sizing.reset-size1.size5,\n.katex .fontsize-ensurer.reset-size1.size5 {\n /* stylelint-disable-next-line */\n font-size: 1.8em;\n}\n\n.katex .sizing.reset-size1.size6,\n.katex .fontsize-ensurer.reset-size1.size6 {\n /* stylelint-disable-next-line */\n font-size: 2em;\n}\n\n.katex .sizing.reset-size1.size7,\n.katex .fontsize-ensurer.reset-size1.size7 {\n /* stylelint-disable-next-line */\n font-size: 2.4em;\n}\n\n.katex .sizing.reset-size1.size8,\n.katex .fontsize-ensurer.reset-size1.size8 {\n /* stylelint-disable-next-line */\n font-size: 2.88em;\n}\n\n.katex .sizing.reset-size1.size9,\n.katex .fontsize-ensurer.reset-size1.size9 {\n /* stylelint-disable-next-line */\n font-size: 3.456em;\n}\n\n.katex .sizing.reset-size1.size10,\n.katex .fontsize-ensurer.reset-size1.size10 {\n /* stylelint-disable-next-line */\n font-size: 4.148em;\n}\n\n.katex .sizing.reset-size1.size11,\n.katex .fontsize-ensurer.reset-size1.size11 {\n /* stylelint-disable-next-line */\n font-size: 4.976em;\n}\n\n.katex .sizing.reset-size2.size1,\n.katex .fontsize-ensurer.reset-size2.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.8333333333em;\n}\n\n.katex .sizing.reset-size2.size2,\n.katex .fontsize-ensurer.reset-size2.size2 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size2.size3,\n.katex .fontsize-ensurer.reset-size2.size3 {\n /* stylelint-disable-next-line */\n font-size: 1.1666666667em;\n}\n\n.katex .sizing.reset-size2.size4,\n.katex .fontsize-ensurer.reset-size2.size4 {\n /* stylelint-disable-next-line */\n font-size: 1.3333333333em;\n}\n\n.katex .sizing.reset-size2.size5,\n.katex .fontsize-ensurer.reset-size2.size5 {\n /* stylelint-disable-next-line */\n font-size: 1.5em;\n}\n\n.katex .sizing.reset-size2.size6,\n.katex .fontsize-ensurer.reset-size2.size6 {\n /* stylelint-disable-next-line */\n font-size: 1.6666666667em;\n}\n\n.katex .sizing.reset-size2.size7,\n.katex .fontsize-ensurer.reset-size2.size7 {\n /* stylelint-disable-next-line */\n font-size: 2em;\n}\n\n.katex .sizing.reset-size2.size8,\n.katex .fontsize-ensurer.reset-size2.size8 {\n /* stylelint-disable-next-line */\n font-size: 2.4em;\n}\n\n.katex .sizing.reset-size2.size9,\n.katex .fontsize-ensurer.reset-size2.size9 {\n /* stylelint-disable-next-line */\n font-size: 2.88em;\n}\n\n.katex .sizing.reset-size2.size10,\n.katex .fontsize-ensurer.reset-size2.size10 {\n /* stylelint-disable-next-line */\n font-size: 3.4566666667em;\n}\n\n.katex .sizing.reset-size2.size11,\n.katex .fontsize-ensurer.reset-size2.size11 {\n /* stylelint-disable-next-line */\n font-size: 4.1466666667em;\n}\n\n.katex .sizing.reset-size3.size1,\n.katex .fontsize-ensurer.reset-size3.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.7142857143em;\n}\n\n.katex .sizing.reset-size3.size2,\n.katex .fontsize-ensurer.reset-size3.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.8571428571em;\n}\n\n.katex .sizing.reset-size3.size3,\n.katex .fontsize-ensurer.reset-size3.size3 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size3.size4,\n.katex .fontsize-ensurer.reset-size3.size4 {\n /* stylelint-disable-next-line */\n font-size: 1.1428571429em;\n}\n\n.katex .sizing.reset-size3.size5,\n.katex .fontsize-ensurer.reset-size3.size5 {\n /* stylelint-disable-next-line */\n font-size: 1.2857142857em;\n}\n\n.katex .sizing.reset-size3.size6,\n.katex .fontsize-ensurer.reset-size3.size6 {\n /* stylelint-disable-next-line */\n font-size: 1.4285714286em;\n}\n\n.katex .sizing.reset-size3.size7,\n.katex .fontsize-ensurer.reset-size3.size7 {\n /* stylelint-disable-next-line */\n font-size: 1.7142857143em;\n}\n\n.katex .sizing.reset-size3.size8,\n.katex .fontsize-ensurer.reset-size3.size8 {\n /* stylelint-disable-next-line */\n font-size: 2.0571428571em;\n}\n\n.katex .sizing.reset-size3.size9,\n.katex .fontsize-ensurer.reset-size3.size9 {\n /* stylelint-disable-next-line */\n font-size: 2.4685714286em;\n}\n\n.katex .sizing.reset-size3.size10,\n.katex .fontsize-ensurer.reset-size3.size10 {\n /* stylelint-disable-next-line */\n font-size: 2.9628571429em;\n}\n\n.katex .sizing.reset-size3.size11,\n.katex .fontsize-ensurer.reset-size3.size11 {\n /* stylelint-disable-next-line */\n font-size: 3.5542857143em;\n}\n\n.katex .sizing.reset-size4.size1,\n.katex .fontsize-ensurer.reset-size4.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.625em;\n}\n\n.katex .sizing.reset-size4.size2,\n.katex .fontsize-ensurer.reset-size4.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.75em;\n}\n\n.katex .sizing.reset-size4.size3,\n.katex .fontsize-ensurer.reset-size4.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.875em;\n}\n\n.katex .sizing.reset-size4.size4,\n.katex .fontsize-ensurer.reset-size4.size4 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size4.size5,\n.katex .fontsize-ensurer.reset-size4.size5 {\n /* stylelint-disable-next-line */\n font-size: 1.125em;\n}\n\n.katex .sizing.reset-size4.size6,\n.katex .fontsize-ensurer.reset-size4.size6 {\n /* stylelint-disable-next-line */\n font-size: 1.25em;\n}\n\n.katex .sizing.reset-size4.size7,\n.katex .fontsize-ensurer.reset-size4.size7 {\n /* stylelint-disable-next-line */\n font-size: 1.5em;\n}\n\n.katex .sizing.reset-size4.size8,\n.katex .fontsize-ensurer.reset-size4.size8 {\n /* stylelint-disable-next-line */\n font-size: 1.8em;\n}\n\n.katex .sizing.reset-size4.size9,\n.katex .fontsize-ensurer.reset-size4.size9 {\n /* stylelint-disable-next-line */\n font-size: 2.16em;\n}\n\n.katex .sizing.reset-size4.size10,\n.katex .fontsize-ensurer.reset-size4.size10 {\n /* stylelint-disable-next-line */\n font-size: 2.5925em;\n}\n\n.katex .sizing.reset-size4.size11,\n.katex .fontsize-ensurer.reset-size4.size11 {\n /* stylelint-disable-next-line */\n font-size: 3.11em;\n}\n\n.katex .sizing.reset-size5.size1,\n.katex .fontsize-ensurer.reset-size5.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.5555555556em;\n}\n\n.katex .sizing.reset-size5.size2,\n.katex .fontsize-ensurer.reset-size5.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.6666666667em;\n}\n\n.katex .sizing.reset-size5.size3,\n.katex .fontsize-ensurer.reset-size5.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.7777777778em;\n}\n\n.katex .sizing.reset-size5.size4,\n.katex .fontsize-ensurer.reset-size5.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.8888888889em;\n}\n\n.katex .sizing.reset-size5.size5,\n.katex .fontsize-ensurer.reset-size5.size5 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size5.size6,\n.katex .fontsize-ensurer.reset-size5.size6 {\n /* stylelint-disable-next-line */\n font-size: 1.1111111111em;\n}\n\n.katex .sizing.reset-size5.size7,\n.katex .fontsize-ensurer.reset-size5.size7 {\n /* stylelint-disable-next-line */\n font-size: 1.3333333333em;\n}\n\n.katex .sizing.reset-size5.size8,\n.katex .fontsize-ensurer.reset-size5.size8 {\n /* stylelint-disable-next-line */\n font-size: 1.6em;\n}\n\n.katex .sizing.reset-size5.size9,\n.katex .fontsize-ensurer.reset-size5.size9 {\n /* stylelint-disable-next-line */\n font-size: 1.92em;\n}\n\n.katex .sizing.reset-size5.size10,\n.katex .fontsize-ensurer.reset-size5.size10 {\n /* stylelint-disable-next-line */\n font-size: 2.3044444444em;\n}\n\n.katex .sizing.reset-size5.size11,\n.katex .fontsize-ensurer.reset-size5.size11 {\n /* stylelint-disable-next-line */\n font-size: 2.7644444444em;\n}\n\n.katex .sizing.reset-size6.size1,\n.katex .fontsize-ensurer.reset-size6.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.5em;\n}\n\n.katex .sizing.reset-size6.size2,\n.katex .fontsize-ensurer.reset-size6.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.6em;\n}\n\n.katex .sizing.reset-size6.size3,\n.katex .fontsize-ensurer.reset-size6.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.7em;\n}\n\n.katex .sizing.reset-size6.size4,\n.katex .fontsize-ensurer.reset-size6.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.8em;\n}\n\n.katex .sizing.reset-size6.size5,\n.katex .fontsize-ensurer.reset-size6.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.9em;\n}\n\n.katex .sizing.reset-size6.size6,\n.katex .fontsize-ensurer.reset-size6.size6 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size6.size7,\n.katex .fontsize-ensurer.reset-size6.size7 {\n /* stylelint-disable-next-line */\n font-size: 1.2em;\n}\n\n.katex .sizing.reset-size6.size8,\n.katex .fontsize-ensurer.reset-size6.size8 {\n /* stylelint-disable-next-line */\n font-size: 1.44em;\n}\n\n.katex .sizing.reset-size6.size9,\n.katex .fontsize-ensurer.reset-size6.size9 {\n /* stylelint-disable-next-line */\n font-size: 1.728em;\n}\n\n.katex .sizing.reset-size6.size10,\n.katex .fontsize-ensurer.reset-size6.size10 {\n /* stylelint-disable-next-line */\n font-size: 2.074em;\n}\n\n.katex .sizing.reset-size6.size11,\n.katex .fontsize-ensurer.reset-size6.size11 {\n /* stylelint-disable-next-line */\n font-size: 2.488em;\n}\n\n.katex .sizing.reset-size7.size1,\n.katex .fontsize-ensurer.reset-size7.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.4166666667em;\n}\n\n.katex .sizing.reset-size7.size2,\n.katex .fontsize-ensurer.reset-size7.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.5em;\n}\n\n.katex .sizing.reset-size7.size3,\n.katex .fontsize-ensurer.reset-size7.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.5833333333em;\n}\n\n.katex .sizing.reset-size7.size4,\n.katex .fontsize-ensurer.reset-size7.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.6666666667em;\n}\n\n.katex .sizing.reset-size7.size5,\n.katex .fontsize-ensurer.reset-size7.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.75em;\n}\n\n.katex .sizing.reset-size7.size6,\n.katex .fontsize-ensurer.reset-size7.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.8333333333em;\n}\n\n.katex .sizing.reset-size7.size7,\n.katex .fontsize-ensurer.reset-size7.size7 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size7.size8,\n.katex .fontsize-ensurer.reset-size7.size8 {\n /* stylelint-disable-next-line */\n font-size: 1.2em;\n}\n\n.katex .sizing.reset-size7.size9,\n.katex .fontsize-ensurer.reset-size7.size9 {\n /* stylelint-disable-next-line */\n font-size: 1.44em;\n}\n\n.katex .sizing.reset-size7.size10,\n.katex .fontsize-ensurer.reset-size7.size10 {\n /* stylelint-disable-next-line */\n font-size: 1.7283333333em;\n}\n\n.katex .sizing.reset-size7.size11,\n.katex .fontsize-ensurer.reset-size7.size11 {\n /* stylelint-disable-next-line */\n font-size: 2.0733333333em;\n}\n\n.katex .sizing.reset-size8.size1,\n.katex .fontsize-ensurer.reset-size8.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.3472222222em;\n}\n\n.katex .sizing.reset-size8.size2,\n.katex .fontsize-ensurer.reset-size8.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.4166666667em;\n}\n\n.katex .sizing.reset-size8.size3,\n.katex .fontsize-ensurer.reset-size8.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.4861111111em;\n}\n\n.katex .sizing.reset-size8.size4,\n.katex .fontsize-ensurer.reset-size8.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.5555555556em;\n}\n\n.katex .sizing.reset-size8.size5,\n.katex .fontsize-ensurer.reset-size8.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.625em;\n}\n\n.katex .sizing.reset-size8.size6,\n.katex .fontsize-ensurer.reset-size8.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.6944444444em;\n}\n\n.katex .sizing.reset-size8.size7,\n.katex .fontsize-ensurer.reset-size8.size7 {\n /* stylelint-disable-next-line */\n font-size: 0.8333333333em;\n}\n\n.katex .sizing.reset-size8.size8,\n.katex .fontsize-ensurer.reset-size8.size8 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size8.size9,\n.katex .fontsize-ensurer.reset-size8.size9 {\n /* stylelint-disable-next-line */\n font-size: 1.2em;\n}\n\n.katex .sizing.reset-size8.size10,\n.katex .fontsize-ensurer.reset-size8.size10 {\n /* stylelint-disable-next-line */\n font-size: 1.4402777778em;\n}\n\n.katex .sizing.reset-size8.size11,\n.katex .fontsize-ensurer.reset-size8.size11 {\n /* stylelint-disable-next-line */\n font-size: 1.7277777778em;\n}\n\n.katex .sizing.reset-size9.size1,\n.katex .fontsize-ensurer.reset-size9.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.2893518519em;\n}\n\n.katex .sizing.reset-size9.size2,\n.katex .fontsize-ensurer.reset-size9.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.3472222222em;\n}\n\n.katex .sizing.reset-size9.size3,\n.katex .fontsize-ensurer.reset-size9.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.4050925926em;\n}\n\n.katex .sizing.reset-size9.size4,\n.katex .fontsize-ensurer.reset-size9.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.462962963em;\n}\n\n.katex .sizing.reset-size9.size5,\n.katex .fontsize-ensurer.reset-size9.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.5208333333em;\n}\n\n.katex .sizing.reset-size9.size6,\n.katex .fontsize-ensurer.reset-size9.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.5787037037em;\n}\n\n.katex .sizing.reset-size9.size7,\n.katex .fontsize-ensurer.reset-size9.size7 {\n /* stylelint-disable-next-line */\n font-size: 0.6944444444em;\n}\n\n.katex .sizing.reset-size9.size8,\n.katex .fontsize-ensurer.reset-size9.size8 {\n /* stylelint-disable-next-line */\n font-size: 0.8333333333em;\n}\n\n.katex .sizing.reset-size9.size9,\n.katex .fontsize-ensurer.reset-size9.size9 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size9.size10,\n.katex .fontsize-ensurer.reset-size9.size10 {\n /* stylelint-disable-next-line */\n font-size: 1.2002314815em;\n}\n\n.katex .sizing.reset-size9.size11,\n.katex .fontsize-ensurer.reset-size9.size11 {\n /* stylelint-disable-next-line */\n font-size: 1.4398148148em;\n}\n\n.katex .sizing.reset-size10.size1,\n.katex .fontsize-ensurer.reset-size10.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.2410800386em;\n}\n\n.katex .sizing.reset-size10.size2,\n.katex .fontsize-ensurer.reset-size10.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.2892960463em;\n}\n\n.katex .sizing.reset-size10.size3,\n.katex .fontsize-ensurer.reset-size10.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.337512054em;\n}\n\n.katex .sizing.reset-size10.size4,\n.katex .fontsize-ensurer.reset-size10.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.3857280617em;\n}\n\n.katex .sizing.reset-size10.size5,\n.katex .fontsize-ensurer.reset-size10.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.4339440694em;\n}\n\n.katex .sizing.reset-size10.size6,\n.katex .fontsize-ensurer.reset-size10.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.4821600771em;\n}\n\n.katex .sizing.reset-size10.size7,\n.katex .fontsize-ensurer.reset-size10.size7 {\n /* stylelint-disable-next-line */\n font-size: 0.5785920926em;\n}\n\n.katex .sizing.reset-size10.size8,\n.katex .fontsize-ensurer.reset-size10.size8 {\n /* stylelint-disable-next-line */\n font-size: 0.6943105111em;\n}\n\n.katex .sizing.reset-size10.size9,\n.katex .fontsize-ensurer.reset-size10.size9 {\n /* stylelint-disable-next-line */\n font-size: 0.8331726133em;\n}\n\n.katex .sizing.reset-size10.size10,\n.katex .fontsize-ensurer.reset-size10.size10 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size10.size11,\n.katex .fontsize-ensurer.reset-size10.size11 {\n /* stylelint-disable-next-line */\n font-size: 1.1996142719em;\n}\n\n.katex .sizing.reset-size11.size1,\n.katex .fontsize-ensurer.reset-size11.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.2009646302em;\n}\n\n.katex .sizing.reset-size11.size2,\n.katex .fontsize-ensurer.reset-size11.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.2411575563em;\n}\n\n.katex .sizing.reset-size11.size3,\n.katex .fontsize-ensurer.reset-size11.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.2813504823em;\n}\n\n.katex .sizing.reset-size11.size4,\n.katex .fontsize-ensurer.reset-size11.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.3215434084em;\n}\n\n.katex .sizing.reset-size11.size5,\n.katex .fontsize-ensurer.reset-size11.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.3617363344em;\n}\n\n.katex .sizing.reset-size11.size6,\n.katex .fontsize-ensurer.reset-size11.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.4019292605em;\n}\n\n.katex .sizing.reset-size11.size7,\n.katex .fontsize-ensurer.reset-size11.size7 {\n /* stylelint-disable-next-line */\n font-size: 0.4823151125em;\n}\n\n.katex .sizing.reset-size11.size8,\n.katex .fontsize-ensurer.reset-size11.size8 {\n /* stylelint-disable-next-line */\n font-size: 0.578778135em;\n}\n\n.katex .sizing.reset-size11.size9,\n.katex .fontsize-ensurer.reset-size11.size9 {\n /* stylelint-disable-next-line */\n font-size: 0.6945337621em;\n}\n\n.katex .sizing.reset-size11.size10,\n.katex .fontsize-ensurer.reset-size11.size10 {\n /* stylelint-disable-next-line */\n font-size: 0.8336012862em;\n}\n\n.katex .sizing.reset-size11.size11,\n.katex .fontsize-ensurer.reset-size11.size11 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .delimsizing.size1 {\n font-family: KaTeX_Size1;\n}\n\n.katex .delimsizing.size2 {\n font-family: KaTeX_Size2;\n}\n\n.katex .delimsizing.size3 {\n font-family: KaTeX_Size3;\n}\n\n.katex .delimsizing.size4 {\n font-family: KaTeX_Size4;\n}\n\n.katex .delimsizing.mult .delim-size1>span {\n font-family: KaTeX_Size1;\n}\n\n.katex .delimsizing.mult .delim-size4>span {\n font-family: KaTeX_Size4;\n}\n\n.katex .nulldelimiter {\n display: inline-block;\n width: 0.12em;\n}\n\n.katex .delimcenter {\n position: relative;\n}\n\n.katex .op-symbol {\n position: relative;\n}\n\n.katex .op-symbol.small-op {\n font-family: KaTeX_Size1;\n}\n\n.katex .op-symbol.large-op {\n font-family: KaTeX_Size2;\n}\n\n.katex .op-limits>.vlist-t {\n text-align: center;\n}\n\n.katex .accent>.vlist-t {\n text-align: center;\n}\n\n.katex .accent .accent-body {\n position: relative;\n}\n\n.katex .accent .accent-body:not(.accent-full) {\n width: 0;\n}\n\n.katex .overlay {\n display: block;\n}\n\n.katex .mtable .vertical-separator {\n display: inline-block;\n min-width: 1px;\n}\n\n.katex .mtable .arraycolsep {\n display: inline-block;\n}\n\n.katex .mtable .col-align-c>.vlist-t {\n text-align: center;\n}\n\n.katex .mtable .col-align-l>.vlist-t {\n text-align: left;\n}\n\n.katex .mtable .col-align-r>.vlist-t {\n text-align: right;\n}\n\n.katex .svg-align {\n text-align: left;\n}\n\n.katex svg {\n display: block;\n position: absolute;\n width: 100%;\n height: inherit;\n fill: currentColor;\n stroke: currentColor;\n fill-rule: nonzero;\n fill-opacity: 1;\n stroke-width: 1;\n stroke-linecap: butt;\n stroke-linejoin: miter;\n stroke-miterlimit: 4;\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n stroke-opacity: 1;\n}\n\n.katex svg path {\n stroke: none;\n}\n\n.katex img {\n border-style: none;\n min-width: 0;\n min-height: 0;\n max-width: none;\n max-height: none;\n}\n\n.katex .stretchy {\n width: 100%;\n display: block;\n position: relative;\n overflow: hidden;\n}\n\n.katex .stretchy::before,\n.katex .stretchy::after {\n content: \"\";\n}\n\n.katex .hide-tail {\n width: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.katex .halfarrow-left {\n position: absolute;\n left: 0;\n width: 50.2%;\n overflow: hidden;\n}\n\n.katex .halfarrow-right {\n position: absolute;\n right: 0;\n width: 50.2%;\n overflow: hidden;\n}\n\n.katex .brace-left {\n position: absolute;\n left: 0;\n width: 25.1%;\n overflow: hidden;\n}\n\n.katex .brace-center {\n position: absolute;\n left: 25%;\n width: 50%;\n overflow: hidden;\n}\n\n.katex .brace-right {\n position: absolute;\n right: 0;\n width: 25.1%;\n overflow: hidden;\n}\n\n.katex .x-arrow-pad {\n padding: 0 0.5em;\n}\n\n.katex .cd-arrow-pad {\n padding: 0 0.55556em 0 0.27778em;\n}\n\n.katex .x-arrow,\n.katex .mover,\n.katex .munder {\n text-align: center;\n}\n\n.katex .boxpad {\n padding: 0 0.3em;\n}\n\n.katex .fbox,\n.katex .fcolorbox {\n box-sizing: border-box;\n border: 0.04em solid;\n}\n\n.katex .cancel-pad {\n padding: 0 0.2em;\n}\n\n.katex .cancel-lap {\n margin-left: -0.2em;\n margin-right: -0.2em;\n}\n\n.katex .sout {\n border-bottom-style: solid;\n border-bottom-width: 0.08em;\n}\n\n.katex .angl {\n box-sizing: border-box;\n border-top: 0.049em solid;\n border-right: 0.049em solid;\n margin-right: 0.03889em;\n}\n\n.katex .anglpad {\n padding: 0 0.03889em;\n}\n\n.katex .eqn-num::before {\n counter-increment: katexEqnNo;\n content: \"(\" counter(katexEqnNo) \")\";\n}\n\n.katex .mml-eqn-num::before {\n counter-increment: mmlEqnNo;\n content: \"(\" counter(mmlEqnNo) \")\";\n}\n\n.katex .mtr-glue {\n width: 50%;\n}\n\n.katex .cd-vert-arrow {\n display: inline-block;\n position: relative;\n}\n\n.katex .cd-label-left {\n display: inline-block;\n position: absolute;\n right: calc(50% + 0.3em);\n text-align: left;\n}\n\n.katex .cd-label-right {\n display: inline-block;\n position: absolute;\n left: calc(50% + 0.3em);\n text-align: right;\n}\n\n.katex-display {\n display: block;\n margin: 1em 0;\n text-align: center;\n}\n\n.katex-display>.katex {\n display: block;\n text-align: center;\n white-space: nowrap;\n}\n\n.katex-display>.katex>.katex-html {\n display: block;\n position: relative;\n}\n\n.katex-display>.katex>.katex-html>.tag {\n position: absolute;\n right: 0;\n}\n\n.katex-display.leqno>.katex>.katex-html>.tag {\n left: 0;\n right: auto;\n}\n\n.katex-display.fleqn>.katex {\n text-align: left;\n padding-left: 2em;\n}\n\nbody {\n counter-reset: katexEqnNo mmlEqnNo;\n}",
29
+ "type": "registry:component",
30
+ "target": "src/cards/markdownViewer/katex.css"
31
+ },
32
+ {
33
+ "path": "cards/markdownViewer/markdownViewer.code.css",
34
+ "content": "/**\n * Code-block styling for the MarkdownViewer card.\n *\n * Works in concert with `highlight.js/styles/github.css` (imported in the\n * component) to give code a GitHub-like appearance:\n *\n * • Fenced code blocks → light-gray bg, rounded border, 13 px mono\n * • Inline code → muted pill, slightly smaller than body text\n *\n * These rules are intentionally scoped to elements that react-markdown\n * produces so they do not bleed into the rest of the application.\n */\n\n/* ── Fenced code blocks ────────────────────────────────────────────────────── */\n\n/* Override highlight.js's own background so the block matches GitHub's\n #f6f8fa rather than pure white, and clamp the font to a readable size. */\ncode.hljs {\n background-color: #f6f8fa;\n font-size: 0.8125rem;\n /* 13 px */\n line-height: 1.65;\n padding: 0;\n /* padding lives on <pre> below */\n border-radius: 0;\n /* radius lives on <pre> below */\n}\n\n/* The <pre> wrapper: add GitHub-style border + padding + corner radius. */\npre:has(code.hljs) {\n background-color: #f6f8fa;\n border: 1px solid #d0d7de;\n border-radius: 0.5rem;\n /* 8 px */\n padding: 1rem;\n overflow-x: auto;\n margin: 0.25rem 0;\n}\n\n/* Dark-mode overrides — keep readable against the dark surface */\n@media (prefers-color-scheme: dark) {\n code.hljs {\n background-color: #161b22;\n color: #c9d1d9;\n }\n\n pre:has(code.hljs) {\n background-color: #161b22;\n border-color: #30363d;\n }\n}\n\n/* ── Typography — headings, paragraphs, lists, etc. ────────────────────────── */\n\n/* Headings: size, weight, and spacing matching GitHub's rendered markdown. */\n.md-prose h1 {\n font-size: 2em;\n font-weight: 600;\n line-height: 1.25;\n padding-bottom: 0.3em;\n border-bottom: 1px solid #d0d7de;\n margin: 1.5rem 0 1rem;\n}\n\n.md-prose h2 {\n font-size: 1.5em;\n font-weight: 600;\n line-height: 1.25;\n padding-bottom: 0.3em;\n border-bottom: 1px solid #d0d7de;\n margin: 1.5rem 0 1rem;\n}\n\n.md-prose h3 {\n font-size: 1.25em;\n font-weight: 600;\n line-height: 1.25;\n margin: 1.25rem 0 0.75rem;\n}\n\n.md-prose h4 {\n font-size: 1em;\n font-weight: 600;\n line-height: 1.25;\n margin: 1rem 0 0.5rem;\n}\n\n.md-prose h5,\n.md-prose h6 {\n font-size: 0.875em;\n font-weight: 600;\n line-height: 1.25;\n margin: 1rem 0 0.5rem;\n}\n\n/* Paragraphs */\n.md-prose p {\n margin: 0 0 1rem;\n line-height: 1.75;\n}\n\n/* Unordered and ordered lists */\n.md-prose ul,\n.md-prose ol {\n padding-left: 2em;\n margin: 0 0 1rem;\n line-height: 1.75;\n}\n\n.md-prose ul {\n list-style-type: disc;\n}\n\n.md-prose ol {\n list-style-type: decimal;\n}\n\n.md-prose li {\n margin: 0.25em 0;\n}\n\n.md-prose li>p {\n margin: 0;\n}\n\n/* Nested lists */\n.md-prose ul ul,\n.md-prose ul ol,\n.md-prose ol ul,\n.md-prose ol ol {\n margin-bottom: 0;\n}\n\n/* Blockquotes */\n.md-prose blockquote {\n border-left: 4px solid #d0d7de;\n padding: 0 1em;\n color: #656d76;\n margin: 0 0 1rem;\n}\n\n.md-prose blockquote>p {\n margin: 0;\n}\n\n/* Horizontal rules */\n.md-prose hr {\n border: 0;\n border-top: 1px solid #d0d7de;\n margin: 1.5rem 0;\n}\n\n/* Links */\n.md-prose a {\n color: #0969da;\n text-decoration: none;\n}\n\n.md-prose a:hover {\n text-decoration: underline;\n}\n\n/* Strong / emphasis */\n.md-prose strong {\n font-weight: 600;\n}\n\n@media (prefers-color-scheme: dark) {\n\n .md-prose h1,\n .md-prose h2 {\n border-bottom-color: #30363d;\n }\n\n .md-prose hr {\n border-top-color: #30363d;\n }\n\n .md-prose blockquote {\n border-left-color: #30363d;\n color: #8b949e;\n }\n\n .md-prose a {\n color: #58a6ff;\n }\n}\n\n/* ── Tables (GitHub-like) ───────────────────────────────────────────────────── */\n\n/* Scoped to .md-prose so these rules never bleed into app-level tables. */\n.md-prose table {\n border-collapse: collapse;\n width: 100%;\n font-size: 0.875rem;\n margin: 0.75rem 0;\n overflow-x: auto;\n display: block;\n}\n\n.md-prose thead tr {\n background-color: #f6f8fa;\n}\n\n.md-prose th,\n.md-prose td {\n border: 1px solid #d0d7de;\n padding: 6px 13px;\n text-align: left;\n white-space: normal;\n}\n\n.md-prose tbody tr:nth-child(even) {\n background-color: #f6f8fa;\n}\n\n@media (prefers-color-scheme: dark) {\n\n .md-prose thead tr,\n .md-prose tbody tr:nth-child(even) {\n background-color: #161b22;\n }\n\n .md-prose th,\n .md-prose td {\n border-color: #30363d;\n }\n}\n\n/* ── Inline code ────────────────────────────────────────────────────────────── */\n\n/* Style inline `code` spans that are NOT inside a fenced block.\n Uses rgba so it stays subtle in both light and dark themes. */\n:not(pre)>code {\n font-size: 0.875em;\n font-family: ui-monospace, \"SFMono-Regular\", \"SF Mono\", Menlo, Consolas,\n \"Liberation Mono\", monospace;\n background-color: rgba(175, 184, 193, 0.2);\n border-radius: 0.25rem;\n /* 4 px */\n padding: 0.15em 0.4em;\n white-space: nowrap;\n}\n\n/* ── Mermaid diagrams ───────────────────────────────────────────────────────── */\n\n/* Reset font size so the page's typography CSS cannot make text larger than\n what Mermaid measured when it calculated node heights. */\n.mermaid-diagram svg,\n.mermaid-diagram foreignObject div,\n.mermaid-diagram foreignObject p,\n.mermaid-diagram foreignObject span {\n font-size: 14px !important;\n}\n\n/* White background on edge labels with padding so descenders (g, p, y …)\n aren't clipped. Belt-and-suspenders alongside themeVariables + inline SVG override. */\n.mermaid-diagram .edgeLabel,\n.mermaid-diagram .edgeLabel span,\n.mermaid-diagram .edgeLabel div,\n.mermaid-diagram .edgeLabel p {\n background: #ffffff !important;\n background-color: #ffffff !important;\n padding: 2px 4px !important;\n line-height: 1.4 !important;\n}\n\n.mermaid-diagram .edgeLabel rect {\n fill: #ffffff !important;\n}",
35
+ "type": "registry:component",
36
+ "target": "src/cards/markdownViewer/markdownViewer.code.css"
37
+ },
38
+ {
39
+ "path": "cards/markdownViewer/markdownViewer.component.tsx",
40
+ "content": "import React, {useEffect, useRef, useState} from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport type {Components} from \"react-markdown\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport remarkMath from \"remark-math\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeKatex from \"rehype-katex\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport mermaid from \"mermaid\";\nimport \"./katex.css\";\nimport \"highlight.js/styles/github.css\";\nimport \"./markdownViewer.code.css\";\n\nimport type {MarkdownViewerProps} from \"./markdownViewer.types\";\n\n/**\n * Lazily initialise Mermaid once per page load (not per component mount).\n * Detects the current dark/light theme from the `<html>` element and picks\n * the matching Mermaid theme so edge labels and diagram colours look correct.\n */\nlet _mermaidReady = false;\nfunction ensureMermaid() {\n if (!_mermaidReady) {\n const isDark = document.documentElement.classList.contains(\"dark\");\n mermaid.initialize({\n startOnLoad: false,\n theme: isDark ? \"dark\" : \"default\",\n themeVariables: {\n // Keep edge label background white so labels don't bleed through arrows\n edgeLabelBackground: \"#ffffff\",\n // Match the font size we enforce in CSS so node sizes are calculated correctly\n fontSize: \"14px\",\n },\n });\n _mermaidReady = true;\n }\n}\n\n/**\n * Renders a single Mermaid diagram asynchronously.\n * The diagram code is passed via the `code` prop (raw text from the fenced block).\n */\nconst MermaidDiagram: React.FC<{code: string}> = ({code}) => {\n const [svg, setSvg] = useState<string>(\"\");\n // Each diagram needs a unique id for mermaid.render()\n const id = useRef(`mermaid-${Math.random().toString(36).slice(2)}`).current;\n\n useEffect(() => {\n ensureMermaid();\n let cancelled = false;\n mermaid\n .render(id, code)\n .then(({svg: rendered}) => {\n if (!cancelled) {\n // Append an override <style> inside the SVG so it takes precedence\n // over Mermaid's own injected styles (which come earlier in the SVG).\n // This removes the opaque background box on edge labels.\n // Note: avoid </x> in template literals — TSX parser misreads them.\n const svgClose = \"<\" + \"/svg>\";\n const styleClose = \"<\" + \"/style>\";\n const overrideStyle =\n \"<style>\" +\n // Reset font size so page CSS can't make text overflow node boxes\n \".mermaid-diagram svg,foreignObject div,foreignObject span,foreignObject p{\" +\n \"font-size:14px!important}\" +\n // White edge-label background with padding so descenders aren't clipped\n \".edgeLabel,.edgeLabel span,.edgeLabel div,.edgeLabel p{\" +\n \"background:#ffffff!important;\" +\n \"background-color:#ffffff!important;\" +\n \"padding:2px 4px!important;\" +\n \"line-height:1.4!important}\" +\n styleClose;\n setSvg(rendered.replace(svgClose, overrideStyle + svgClose));\n }\n })\n .catch((err) => {\n if (!cancelled) setSvg(`<pre style=\"color:red\">${String(err)}</pre>`);\n });\n return () => {\n cancelled = true;\n };\n }, [id, code]);\n\n if (!svg) return null;\n // mermaid.render() returns a sanitised SVG string — safe to inject directly.\n return (\n <div className=\"mermaid-diagram\" dangerouslySetInnerHTML={{__html: svg}} />\n );\n};\n\n/**\n * Default component overrides applied to every MarkdownViewer instance.\n *\n * Goals:\n * - Fenced ```mermaid``` blocks → rendered as Mermaid SVG diagrams\n * - Fenced code blocks → GitHub-like appearance (CSS + rehype-highlight)\n * - Inline `code` spans → subtle muted-pill style (CSS)\n *\n * The `node` prop injected by react-markdown is destructured and discarded\n * here so it doesn't propagate to native DOM elements and trigger React\n * \"unknown prop\" warnings.\n */\nconst DEFAULT_COMPONENTS: Components = {\n pre: ({node: _node, children, ...rest}) => <pre {...rest}>{children}</pre>,\n code: ({node: _node, className, children, ...rest}) => {\n const lang = /language-(\\w+)/.exec(className ?? \"\")?.[1];\n if (lang === \"mermaid\") {\n return <MermaidDiagram code={String(children).replace(/\\n$/, \"\")} />;\n }\n return (\n <code className={className} {...rest}>\n {children}\n </code>\n );\n },\n};\n\nexport const MarkdownViewerComponent = (\n props: PiCardProps<MarkdownViewerProps>,\n): React.ReactNode => {\n const {\n source,\n path,\n maxBodyLength = -1,\n remarkPlugins = [remarkMath, remarkGfm],\n rehypePlugins = [rehypeKatex, rehypeHighlight],\n remarkRehypeOptions,\n components,\n className,\n style = {},\n cardName,\n _cls,\n } = props;\n\n const [fetchedText, setFetchedText] = useState(\"\");\n const text = source || fetchedText;\n\n if (path && fetchedText === \"\") {\n fetch(path)\n .then((response) => response.text())\n .then((fetched) => setFetchedText(fetched));\n }\n\n function decode(s: string): string {\n try {\n return decodeURI(s);\n } catch {\n // may fail if actually not encoded and contains '%'\n return s;\n }\n }\n\n function shortenBody(body: string): string {\n if (body.length <= maxBodyLength) return body;\n\n const shortened = body\n .split(\" \")\n .reduce(\n (p, el) => {\n const [a, cnt] = p;\n if (cnt < maxBodyLength) {\n return [a.concat(el), cnt + el.length + 1] as [string[], number];\n } else {\n return p;\n }\n },\n [[], 0] as [string[], number],\n )[0]\n .join(\" \");\n return shortened.length < body.length ? `${shortened}...` : body;\n }\n\n const decoded = decode(text);\n const content = maxBodyLength > 0 ? shortenBody(decoded) : decoded;\n\n // Merge caller-supplied component overrides on top of our defaults.\n const mergedComponents: Components = {...DEFAULT_COMPONENTS, ...components};\n\n return (\n <div\n className={_cls(\"root\", className)}\n style={style}\n data-pihanga={cardName}\n >\n <ReactMarkdown\n remarkPlugins={remarkPlugins}\n rehypePlugins={\n rehypePlugins as Parameters<typeof ReactMarkdown>[0][\"rehypePlugins\"]\n }\n remarkRehypeOptions={remarkRehypeOptions}\n components={mergedComponents}\n className={`${_cls(\"inner\")} md-prose`}\n >\n {content}\n </ReactMarkdown>\n </div>\n );\n};\n",
41
+ "type": "registry:component",
42
+ "target": "src/cards/markdownViewer/markdownViewer.component.tsx"
43
+ },
44
+ {
45
+ "path": "cards/markdownViewer/markdownViewer.types.ts",
46
+ "content": "import {createCardDeclaration} from \"@pihanga2/core\";\nimport type {PluggableList} from \"unified\";\nimport type {Components} from \"react-markdown\";\n\nexport const MARKDOWN_CARD = \"markdown-viewer\";\n\nexport const MarkdownViewer =\n createCardDeclaration<MarkdownViewerProps>(MARKDOWN_CARD);\n\nexport type MarkdownViewerProps<S = object> = {\n /** Markdown source string to render directly. */\n source?: string;\n /** URL/path to a file whose text content will be fetched and rendered. */\n path?: string;\n /**\n * Truncate rendered text to approximately this many characters (word boundary).\n * A negative value (default: -1) disables truncation.\n */\n maxBodyLength?: number;\n\n remarkPlugins?: PluggableList;\n rehypePlugins?: PluggableList;\n remarkRehypeOptions?: object;\n\n /**\n * Custom element renderers passed directly to `react-markdown`.\n * Merged on top of the card's built-in defaults (code/pre styling).\n * Use this to override how individual HTML elements are rendered.\n */\n components?: Components;\n\n /** Additional CSS class to apply to the outer wrapper div. */\n className?: string;\n style?: S;\n};\n",
47
+ "type": "registry:component",
48
+ "target": "src/cards/markdownViewer/markdownViewer.types.ts"
49
+ }
50
+ ]
51
+ }
package/r/menu.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "menu",
4
+ "type": "registry:component",
5
+ "description": "Pihanga menu card component",
6
+ "dependencies": [
7
+ "@radix-ui/react-menubar@^1.1.16",
8
+ "lucide-react@^0.513.0"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
12
+ "menubar"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "cards/menu/index.ts",
17
+ "content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {MenuComponent} from \"./menu.component\";\nimport {MENU_ACTION, MENU_CARD} from \"./menu.types\";\n\nexport * from \"./menu.types\";\n\nregisterCardComponent({\n name: MENU_CARD,\n component: MenuComponent,\n events: actionTypesToEvents(MENU_ACTION),\n});\n",
18
+ "type": "registry:component",
19
+ "target": "src/cards/menu/index.ts"
20
+ },
21
+ {
22
+ "path": "cards/menu/menu.component.tsx",
23
+ "content": "import React from \"react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {\n Menubar,\n MenubarContent,\n MenubarItem,\n MenubarMenu,\n MenubarSeparator,\n MenubarTrigger,\n} from \"@/components/ui/menubar\";\nimport type {MenuEvents, MenuProps} from \"./menu.types\";\n\nexport const MenuComponent = (\n props: PiCardProps<MenuProps, MenuEvents>,\n): React.ReactNode => {\n const {menuButton, items, className, cardName, onClicked} = props;\n\n return (\n <Menubar className={className} data-pihanga={cardName}>\n <MenubarMenu>\n <MenubarTrigger disabled={menuButton.isDisabled}>\n {menuButton.label}\n </MenubarTrigger>\n <MenubarContent>\n {items.map((item, idx) => {\n if (item === null) {\n return <MenubarSeparator key={idx} />;\n }\n return (\n <MenubarItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => onClicked({itemID: item.id})}\n >\n {item.title ?? item.id}\n </MenubarItem>\n );\n })}\n </MenubarContent>\n </MenubarMenu>\n </Menubar>\n );\n};\n",
24
+ "type": "registry:component",
25
+ "target": "src/cards/menu/menu.component.tsx"
26
+ },
27
+ {
28
+ "path": "cards/menu/menu.types.ts",
29
+ "content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\nimport type {ColorT, DecoratorT, SizeT, VariantT} from \"../types\";\n\nexport const MENU_CARD = \"shad/menu\";\nexport const Menu = createCardDeclaration<MenuProps, MenuEvents>(MENU_CARD);\n\nexport const MENU_ACTION = registerActions(MENU_CARD, [\"clicked\"]);\n\nexport const onMenuClicked = createOnAction<MenuClickEvent>(\n MENU_ACTION.CLICKED,\n);\n\n// ---------------------------------------------------------------------------\n// Shared button props (re-used by trigger buttons)\n// ---------------------------------------------------------------------------\n\nexport type BaseButtonProps = {\n label: string;\n tooltip?: string;\n isDisabled?: boolean;\n isLoading?: boolean;\n loadingPosition?: \"center\" | \"start\" | \"end\";\n size?: SizeT;\n color?: ColorT;\n variant?: VariantT;\n startDecorator?: DecoratorT;\n endDecorator?: DecoratorT;\n};\n\n// ---------------------------------------------------------------------------\n// Menu item types\n// ---------------------------------------------------------------------------\n\nexport type MenuItemT = {\n id: string;\n title?: string;\n disabled?: boolean;\n color?: ColorT;\n variant?: VariantT;\n startDecorator?: DecoratorT;\n endDecorator?: DecoratorT;\n};\n\n/** A `null` entry renders as a visual separator between menu items. */\nexport type MenuSeparatorT = null;\n\n// ---------------------------------------------------------------------------\n// Placement\n// ---------------------------------------------------------------------------\n\nexport type PlacementT =\n | \"auto-end\"\n | \"auto-start\"\n | \"auto\"\n | \"bottom-end\"\n | \"bottom-start\"\n | \"bottom\"\n | \"left-end\"\n | \"left-start\"\n | \"left\"\n | \"right-end\"\n | \"right-start\"\n | \"right\"\n | \"top-end\"\n | \"top-start\"\n | \"top\";\n\n// ---------------------------------------------------------------------------\n// Card props & events\n// ---------------------------------------------------------------------------\n\nexport type MenuProps<S = unknown, T = unknown> = {\n /** Configuration for the trigger button that opens the menu. */\n menuButton: BaseButtonProps;\n /** Ordered list of menu items; `null` entries render as separators. */\n items: (MenuItemT | MenuSeparatorT)[];\n placement?: PlacementT;\n size?: SizeT;\n color?: ColorT;\n variant?: VariantT;\n className?: string;\n style?: S;\n theme?: T;\n};\n\nexport type MenuClickEvent = {\n /** The `id` of the menu item that was clicked. */\n itemID: string;\n};\n\nexport type MenuEvents = {\n onClicked: MenuClickEvent;\n};\n",
30
+ "type": "registry:component",
31
+ "target": "src/cards/menu/menu.types.ts"
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "modeToggle",
4
+ "type": "registry:component",
5
+ "description": "Pihanga modeToggle card component",
6
+ "dependencies": [
7
+ "class-variance-authority@^0.7.1",
8
+ "lucide-react@^0.513.0",
9
+ "radix-ui@^1.4.3"
10
+ ],
11
+ "registryDependencies": [
12
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
13
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-ui-extras.json",
14
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-theme-provider.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "cards/modeToggle/index.ts",
19
+ "content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {ModeToggleComponent} from \"./mode-toggle.component\";\nimport {MODE_TOGGLE_ACTION, MODE_TOGGLE_CARD} from \"./mode-toggle.types\";\n\nexport * from \"./mode-toggle.types\";\n\nregisterCardComponent({\n name: MODE_TOGGLE_CARD,\n component: ModeToggleComponent,\n events: actionTypesToEvents(MODE_TOGGLE_ACTION),\n});\n",
20
+ "type": "registry:component",
21
+ "target": "src/cards/modeToggle/index.ts"
22
+ },
23
+ {
24
+ "path": "cards/modeToggle/mode-toggle.component.tsx",
25
+ "content": "import React from \"react\";\nimport {Moon, Sun} from \"lucide-react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {Button} from \"@/components/ui/button\";\nimport {useTheme} from \"@/components/theme-provider\";\nimport type {ModeToggleEvents, ModeToggleProps} from \"./mode-toggle.types\";\n\nexport const ModeToggleComponent = (\n props: PiCardProps<ModeToggleProps, ModeToggleEvents>,\n): React.ReactNode => {\n const {setTheme, theme} = useTheme();\n const {variant, className, onModeChanged, cardName} = props;\n\n function onClick() {\n const mode = theme === \"dark\" ? \"light\" : \"dark\";\n setTheme(mode);\n onModeChanged({mode});\n }\n\n return (\n <Button\n variant={variant ?? \"outline\"}\n size=\"icon\"\n onClick={onClick}\n className={className}\n data-pihanga={cardName}\n >\n <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n <span className=\"sr-only\">Toggle theme</span>\n </Button>\n );\n};\n",
26
+ "type": "registry:component",
27
+ "target": "src/cards/modeToggle/mode-toggle.component.tsx"
28
+ },
29
+ {
30
+ "path": "cards/modeToggle/mode-toggle.types.ts",
31
+ "content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\nimport type {VariantT} from \"../types\";\n\nexport const MODE_TOGGLE_CARD = \"shad/mode-toggle\";\nexport const ModeToggle = createCardDeclaration<\n ModeToggleProps,\n ModeToggleEvents\n>(MODE_TOGGLE_CARD);\n\nexport const MODE_TOGGLE_ACTION = registerActions(MODE_TOGGLE_CARD, [\n \"mode_changed\",\n]);\n\nexport const onModeToggleChanged = createOnAction<ModeChangeEvent>(\n MODE_TOGGLE_ACTION.MODE_CHANGED,\n);\n\n// ---------------------------------------------------------------------------\n// Card props & events\n// ---------------------------------------------------------------------------\n\nexport type ModeToggleProps = {\n /** Visual variant of the toggle button. Defaults to `\"outline\"`. */\n variant?: VariantT;\n className?: string;\n};\n\nexport type ModeChangeEvent = {\n mode: \"light\" | \"dark\" | \"system\";\n};\n\nexport type ModeToggleEvents = {\n onModeChanged: ModeChangeEvent;\n};\n",
32
+ "type": "registry:component",
33
+ "target": "src/cards/modeToggle/mode-toggle.types.ts"
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "navbarSearch",
4
+ "type": "registry:component",
5
+ "description": "Pihanga navbarSearch card component",
6
+ "dependencies": [],
7
+ "registryDependencies": [
8
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
9
+ "input"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "cards/navbarSearch/index.ts",
14
+ "content": "import {\n actionTypesToEvents,\n registerCardComponent,\n} from \"@pihanga2/core\";\n\nimport {Component} from \"./navbarSearch.component\";\nimport {NAVBAR_SEARCH_ACTION, NAVBAR_SEARCH_CARD} from \"./navbarSearch.type\";\n\nexport * from \"./navbarSearch.type\";\n\nregisterCardComponent({\n name: NAVBAR_SEARCH_CARD,\n component: Component,\n events: actionTypesToEvents(NAVBAR_SEARCH_ACTION),\n});\n",
15
+ "type": "registry:component",
16
+ "target": "src/cards/navbarSearch/index.ts"
17
+ },
18
+ {
19
+ "path": "cards/navbarSearch/navbarSearch.component.tsx",
20
+ "content": "import {Input} from \"@/components/ui/input\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport type {\n NavbarSearchEvents,\n NavbarSearchProps,\n} from \"./navbarSearch.type\";\n\nexport const Component = (\n props: PiCardProps<NavbarSearchProps, NavbarSearchEvents>,\n): React.ReactNode => {\n const {\n searchItems,\n placeholder = \"Load Spec...\",\n onSearchSubmit,\n onSearchItems,\n onSearchFocus,\n cardName,\n } = props;\n const listId = searchItems?.length ? `${cardName}-search` : undefined;\n\n const submitSearch = (value: string, input?: HTMLInputElement | null) => {\n const search = value.trim();\n if (!search) return;\n onSearchSubmit?.({search});\n if (input) {\n input.value = \"\";\n emitSearchInput(\"\");\n }\n input?.blur();\n };\n\n const emitSearchInput = (value: string) => {\n onSearchItems?.({search: value});\n };\n\n const maybeSubmit = (value: string, input?: HTMLInputElement | null) => {\n if (searchItems?.includes(value)) {\n submitSearch(value, input);\n }\n };\n\n const openNativeSuggestions = (input: HTMLInputElement | null) => {\n if (!input) {\n return;\n }\n\n const pickerInput = input as HTMLInputElement & {\n showPicker?: () => void;\n };\n pickerInput.showPicker?.();\n };\n\n const blockManualTyping = (event: React.KeyboardEvent<HTMLInputElement>) => {\n const allowedKeys = new Set([\n \"Tab\",\n \"Enter\",\n \"Escape\",\n \"ArrowDown\",\n \"ArrowUp\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Home\",\n \"End\",\n \"Shift\",\n \"Control\",\n \"Alt\",\n \"Meta\",\n ]);\n\n if (event.ctrlKey || event.metaKey || event.altKey) {\n return;\n }\n\n if (allowedKeys.has(event.key)) {\n if (event.key === \"ArrowDown\") {\n openNativeSuggestions(event.currentTarget);\n }\n return;\n }\n\n event.preventDefault();\n };\n\n return (\n <form\n onSubmit={(el) => {\n el.preventDefault();\n const data = new FormData(el.currentTarget);\n submitSearch(\n String(data.get(\"search\") || \"\"),\n el.currentTarget.elements.namedItem(\"search\") as HTMLInputElement | null,\n );\n }}\n autoComplete=\"off\"\n className=\"ml-auto flex-1 sm:flex-initial\"\n data-pihanga={cardName}\n >\n <div className=\"relative\">\n <Input\n type=\"search\"\n placeholder={placeholder}\n name=\"search\"\n list={listId}\n autoComplete=\"off\"\n className=\"sm:w-[300px] md:w-[200px] lg:w-[300px]\"\n onFocus={() => {\n onSearchFocus?.({});\n }}\n onClick={(e) => {\n openNativeSuggestions(e.currentTarget);\n }}\n onBeforeInput={(e) => {\n e.preventDefault();\n }}\n onKeyDown={blockManualTyping}\n onInput={(e) => {\n const input = e.target as HTMLInputElement;\n const value = input.value;\n emitSearchInput(value);\n maybeSubmit(value, input);\n }}\n onChange={(e) => {\n const input = e.target as HTMLInputElement;\n const value = input.value;\n emitSearchInput(value);\n maybeSubmit(value, input);\n }}\n onBlur={(e) => {\n const input = e.target as HTMLInputElement;\n const value = input.value;\n maybeSubmit(value, input);\n }}\n />\n {listId && (\n <datalist id={listId}>\n {searchItems!.map((item) => (\n <option key={item} value={item} />\n ))}\n </datalist>\n )}\n </div>\n </form>\n );\n};\n",
21
+ "type": "registry:component",
22
+ "target": "src/cards/navbarSearch/navbarSearch.component.tsx"
23
+ },
24
+ {
25
+ "path": "cards/navbarSearch/navbarSearch.type.ts",
26
+ "content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const NAVBAR_SEARCH_CARD = \"navbarSearch\";\nexport const NavbarSearch = createCardDeclaration<\n NavbarSearchProps,\n NavbarSearchEvents\n>(NAVBAR_SEARCH_CARD);\n\nexport const NAVBAR_SEARCH_ACTION = registerActions(NAVBAR_SEARCH_CARD, [\n \"search_submit\",\n \"search_items\",\n \"search_focus\",\n]);\n\nexport const onNavbarSearchSubmit =\n createOnAction<NavbarSearchEvent>(NAVBAR_SEARCH_ACTION.SEARCH_SUBMIT);\nexport const onNavbarSearchItems = createOnAction<NavbarSearchInputEvent>(\n NAVBAR_SEARCH_ACTION.SEARCH_ITEMS,\n);\nexport const onNavbarSearchFocus = createOnAction<NavbarSearchFocusEvent>(\n NAVBAR_SEARCH_ACTION.SEARCH_FOCUS,\n);\n\nexport type NavbarSearchProps = {\n searchItems?: string[];\n placeholder?: string;\n};\n\nexport type NavbarSearchEvent = {search: string};\nexport type NavbarSearchInputEvent = {search: string};\nexport type NavbarSearchFocusEvent = Record<string, never>;\n\nexport type NavbarSearchEvents = {\n onSearchSubmit: NavbarSearchEvent;\n onSearchItems?: NavbarSearchInputEvent;\n onSearchFocus?: NavbarSearchFocusEvent;\n};\n",
27
+ "type": "registry:component",
28
+ "target": "src/cards/navbarSearch/navbarSearch.type.ts"
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "pageWithNavbar",
4
+ "type": "registry:component",
5
+ "description": "Pihanga pageWithNavbar card component",
6
+ "dependencies": [
7
+ "@radix-ui/react-dialog@^1.1.15",
8
+ "class-variance-authority@^0.7.1",
9
+ "clsx@^2.1.1",
10
+ "lucide-react@^0.513.0",
11
+ "radix-ui@^1.4.3"
12
+ ],
13
+ "registryDependencies": [
14
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
15
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-ui-extras.json",
16
+ "sheet",
17
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-cards-icons.json",
18
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-cards-types.json"
19
+ ],
20
+ "files": [
21
+ {
22
+ "path": "cards/pageWithNavbar/index.ts",
23
+ "content": "import {\n actionTypesToEvents,\n createCardDeclaration,\n registerCardComponent,\n} from \"@pihanga2/core\";\n\nimport {Component} from \"./pageWithNavbar.component\";\nimport {PAGE_WITH_NAVBAR_ACTION} from \"./pageWithNavbar.type\";\n\nexport * from \"./pageWithNavbar.type\";\n\nconst CARD_NAME = \"shad/pageWithNavbar\";\n\nexport const SdPageWithNavbar = createCardDeclaration(CARD_NAME);\n\nregisterCardComponent({\n name: CARD_NAME,\n component: Component,\n events: actionTypesToEvents(PAGE_WITH_NAVBAR_ACTION),\n});\n",
24
+ "type": "registry:component",
25
+ "target": "src/cards/pageWithNavbar/index.ts"
26
+ },
27
+ {
28
+ "path": "cards/pageWithNavbar/pageWithNavbar.component.tsx",
29
+ "content": "import {Menu} from \"lucide-react\";\nimport clsx from \"clsx\";\nimport {useEffect, useRef, useState} from \"react\";\n\nimport {Button} from \"@/components/ui/button\";\nimport {Sheet, SheetContent, SheetTrigger} from \"@/components/ui/sheet\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {getIconElement} from \"@/cards/icons\";\nimport {ScreenSize, SCREEN_SIZE_ORDER, SCREEN_SIZE_WIDTHS} from \"@/cards/types\";\nimport type {\n PageWithNavbarEvents,\n PageWithNavbarProps,\n ResponsiveCardRef,\n} from \"./pageWithNavbar.type\";\nimport type {SizeMap} from \"@/cards/types\";\nimport type {PiCardRef} from \"@pihanga2/core\";\n\nimport \"./pageWithNavbar.css\";\n\n// ---------------------------------------------------------------------------\n// Style override type — consumers can extend this to add Tailwind class names\n// to any structural section of the layout.\n// ---------------------------------------------------------------------------\n\nexport type ShadStyle = {\n shad?: {\n window?: string;\n page?: string;\n header?: string;\n main?: string;\n footer?: string;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Screen-size hook\n// ---------------------------------------------------------------------------\n\nfunction getCurrentScreenSize(): ScreenSize {\n const w = typeof window !== \"undefined\" ? window.innerWidth : 0;\n // Walk breakpoints from largest to smallest\n for (let i = SCREEN_SIZE_ORDER.length - 1; i >= 0; i--) {\n const size = SCREEN_SIZE_ORDER[i];\n if (w >= SCREEN_SIZE_WIDTHS[size]) return size;\n }\n return ScreenSize.XS;\n}\n\nfunction useScreenSize(): ScreenSize {\n const [size, setSize] = useState<ScreenSize>(getCurrentScreenSize);\n\n useEffect(() => {\n const handleResize = () => setSize(getCurrentScreenSize());\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n return size;\n}\n\n// ---------------------------------------------------------------------------\n// Responsive card-ref resolution\n// ---------------------------------------------------------------------------\n\nfunction isSizeMap(ref: ResponsiveCardRef): ref is SizeMap<PiCardRef> {\n return typeof ref === \"object\" && ref !== null;\n}\n\n/**\n * Resolve a {@link ResponsiveCardRef} to a concrete `PiCardRef` (or\n * `undefined`) for the given screen size.\n *\n * Uses a **mobile-first cascade**: the entry with the largest breakpoint\n * that is still ≤ `currentSize` wins; falls back to `default` otherwise.\n */\nfunction resolveCardRef(\n ref: ResponsiveCardRef | undefined,\n currentSize: ScreenSize,\n): PiCardRef | undefined {\n if (!ref) return undefined;\n if (!isSizeMap(ref)) return ref; // plain PiCardRef (string or PiCardDef)\n\n // Cascade: find the highest breakpoint that is ≤ current size\n const currentIdx = SCREEN_SIZE_ORDER.indexOf(currentSize);\n for (let i = currentIdx; i >= 0; i--) {\n const size = SCREEN_SIZE_ORDER[i];\n if (Object.prototype.hasOwnProperty.call(ref, size)) {\n const val = ref[size];\n // null means \"explicitly hidden at this breakpoint\"\n return val ?? undefined;\n }\n }\n // Fall back to the default entry\n return ref.default ?? undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Component = (\n props: PiCardProps<PageWithNavbarProps<ShadStyle>, PageWithNavbarEvents>,\n): React.ReactNode => {\n const {\n title,\n iconName,\n main,\n footer,\n navLinks = [],\n scrollResetKey,\n headerLeftCard,\n headerRightCard,\n style,\n onNavigateTo,\n cardName,\n } = props;\n\n const screenSize = useScreenSize();\n const cl = style?.shad ?? {};\n const mainRef = useRef<HTMLElement | null>(null);\n\n // Scroll main back to top when the logical \"page\" changes\n useEffect(() => {\n mainRef.current?.scrollTo({top: 0, behavior: \"auto\"});\n }, [scrollResetKey]);\n\n const resolvedLeftCard = resolveCardRef(headerLeftCard, screenSize);\n const resolvedRightCard = resolveCardRef(headerRightCard, screenSize);\n const hasNavLinks = navLinks.length > 0;\n\n // ------------------------------------------------------------------\n // Sub-components\n // ------------------------------------------------------------------\n\n /** Title section: optional icon + text */\n function TitleSection({className}: {className?: string}) {\n const IconEl = iconName ? getIconElement(iconName) : undefined;\n return (\n <div className={clsx(\"title-section\", className)}>\n {IconEl && <IconEl className=\"title-icon\" aria-hidden />}\n <span className=\"title-text\">{title}</span>\n </div>\n );\n }\n\n /** Nav bar rendered on medium screens and above */\n function NavMd() {\n return (\n <nav className=\"nav-md\" aria-label=\"Main navigation\">\n <TitleSection />\n {navLinks.map((el) => (\n <Button\n key={el.id}\n onClick={() => onNavigateTo({id: el.id})}\n variant=\"link\"\n className=\"nav-link\"\n >\n {el.title ?? capitalize(el.id)}\n </Button>\n ))}\n </nav>\n );\n }\n\n /** Hamburger trigger that opens a slide-out sheet on small screens */\n function NavSm() {\n return (\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"shrink-0 md:hidden\"\n aria-label=\"Toggle navigation menu\"\n >\n <Menu className=\"h-5 w-5\" aria-hidden />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\">\n <nav className=\"nav-sm\" aria-label=\"Mobile navigation\">\n <TitleSection />\n {navLinks.map((el) => (\n <Button\n key={el.id}\n onClick={() => onNavigateTo({id: el.id})}\n variant=\"link\"\n className=\"nav-link\"\n >\n {el.title ?? capitalize(el.id)}\n </Button>\n ))}\n </nav>\n </SheetContent>\n </Sheet>\n );\n }\n\n // ------------------------------------------------------------------\n // Layout\n // ------------------------------------------------------------------\n\n return (\n <div className={clsx(\"window\", cl.window)} data-pihanga={cardName}>\n <div className={clsx(\"page\", cl.page)}>\n {/* ── Header ─────────────────────────────────────────────── */}\n <header className={clsx(\"header\", cl.header)}>\n {/* Medium+ nav (title + links) */}\n <NavMd />\n\n {/* Small-screen hamburger (only when there are nav links) */}\n {hasNavLinks && <NavSm />}\n\n {/* On small screens with no nav links, show the title inline.\n Wrap in a plain div so md:hidden isn't overridden by the\n .title-section CSS class that sets display:inline-flex. */}\n {!hasNavLinks && (\n <div className=\"md:hidden\">\n <TitleSection />\n </div>\n )}\n\n {/* Push action cards to the right */}\n <div className=\"header-actions\">\n {resolvedLeftCard && (\n <Card cardName={resolvedLeftCard} parentCard={cardName} />\n )}\n {resolvedRightCard && (\n <Card cardName={resolvedRightCard} parentCard={cardName} />\n )}\n </div>\n </header>\n\n {/* ── Main ───────────────────────────────────────────────── */}\n <main className={clsx(\"main\", cl.main)} ref={mainRef}>\n <Card cardName={main} parentCard={cardName} />\n </main>\n\n {/* ── Footer (optional) ──────────────────────────────────── */}\n {footer && (\n <footer className={clsx(\"footer\", cl.footer)}>\n <Card cardName={footer} parentCard={cardName} />\n </footer>\n )}\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction capitalize(str: string): string {\n if (!str) return \"\";\n return str.charAt(0).toLocaleUpperCase() + str.slice(1);\n}\n",
30
+ "type": "registry:component",
31
+ "target": "src/cards/pageWithNavbar/pageWithNavbar.component.tsx"
32
+ },
33
+ {
34
+ "path": "cards/pageWithNavbar/pageWithNavbar.css",
35
+ "content": "@import \"tailwindcss\";\n\n/* ============================================================\n Outermost wrapper — centres the page horizontally\n ============================================================ */\n.window {\n @apply flex w-full justify-center;\n}\n\n/* ============================================================\n Page grid: header / main / footer (footer row is auto so it\n collapses gracefully when the footer element is absent)\n ============================================================ */\n.page {\n @apply w-full min-h-screen h-screen;\n display: grid;\n grid-template-areas:\n \"header\"\n \"main\"\n \"footer\";\n grid-template-rows: 4rem 1fr auto;\n grid-template-columns: 100%;\n}\n\n/* ============================================================\n Header\n ============================================================ */\n.header {\n @apply flex items-center gap-4 border-b bg-(--background) px-4 md:px-6;\n grid-area: header;\n}\n\n/* Title section (icon + text) */\n.title-section {\n @apply inline-flex items-center gap-2;\n}\n\n.title-section .title-icon {\n @apply h-6 w-6 shrink-0;\n}\n\n.title-section .title-text {\n @apply text-lg font-semibold text-(--foreground);\n}\n\n/* Medium-and-above horizontal nav */\n.nav-md {\n @apply hidden md:flex flex-row items-center gap-5 lg:gap-6;\n\n .nav-link {\n @apply text-(--muted-foreground) transition-colors hover:text-(--foreground) px-0;\n }\n}\n\n/* Small-screen slide-out nav content */\n.nav-sm {\n @apply grid gap-6 text-lg font-medium p-4;\n\n .nav-link {\n @apply text-(--muted-foreground) hover:text-(--foreground) justify-start px-0 text-lg;\n }\n}\n\n/* Right-aligned action cards area */\n.header-actions {\n @apply ml-auto flex items-center gap-2 lg:gap-4;\n}\n\n/* ============================================================\n Main — scrollable content area\n ============================================================ */\n.main {\n @apply bg-(--background) p-4 py-2 md:p-8 md:py-4;\n grid-area: main;\n overflow-y: auto;\n min-height: 0;\n}\n\n/* ============================================================\n Footer (optional)\n ============================================================ */\n.footer {\n @apply bg-(--background) text-(--muted-foreground) px-4 py-2 md:px-8 md:py-4;\n grid-area: footer;\n}",
36
+ "type": "registry:component",
37
+ "target": "src/cards/pageWithNavbar/pageWithNavbar.css"
38
+ },
39
+ {
40
+ "path": "cards/pageWithNavbar/pageWithNavbar.type.ts",
41
+ "content": "import {\n createCardDeclaration,\n createOnAction,\n type PiCardRef,\n registerActions,\n} from \"@pihanga2/core\";\nimport type {SizeMap} from \"@/cards/types\";\n\nexport const PAGE_WITH_NAVBAR_CARD = \"pageWithNavbar\";\nexport const PageWithNavbar = createCardDeclaration<\n PageWithNavbarProps,\n PageWithNavbarEvents\n>(PAGE_WITH_NAVBAR_CARD);\n\nexport const PAGE_WITH_NAVBAR_ACTION = registerActions(PAGE_WITH_NAVBAR_CARD, [\n \"navigate_to\",\n]);\n\nexport const onPageWithNavbarNavigateTo =\n createOnAction<PageWithNavbarNavigateToEvent>(\n PAGE_WITH_NAVBAR_ACTION.NAVIGATE_TO,\n );\n\n/**\n * A card reference that can optionally vary by screen size.\n *\n * Pass a plain `PiCardRef` string to always show the same card, or a\n * {@link SizeMap} to show different cards (or none) at each breakpoint.\n *\n * @example\n * // Always show the same card\n * headerRightCard: \"myAccountCard\"\n *\n * // Show on md and above only\n * headerRightCard: { [ScreenSize.MD]: \"myAccountCard\" }\n *\n * // Different card per breakpoint\n * headerLeftCard: {\n * [ScreenSize.XS]: \"mobileSearchCard\",\n * [ScreenSize.MD]: \"desktopSearchCard\",\n * }\n */\nexport type ResponsiveCardRef = PiCardRef | SizeMap<PiCardRef>;\n\nexport type PageWithNavbarProps<S = unknown> = {\n /** Page title displayed in the header. */\n title: string;\n\n /**\n * Name of an icon registered via {@link registerIcon} in `icons.ts`.\n * When provided, the icon is rendered next to the title in the header.\n */\n iconName?: string;\n\n /** The main content card. */\n main: PiCardRef;\n\n /**\n * Optional footer card.\n * When omitted the footer row is not rendered.\n */\n footer?: PiCardRef;\n\n /**\n * Navigation links rendered in the header nav bar.\n * On small screens they are collapsed behind a hamburger menu.\n */\n navLinks?: NavLink[];\n\n /**\n * When this value changes the main content area scrolls back to the top.\n * Set it to any string that is unique per logical \"page load\" — typically\n * the URL or ID of the currently loaded resource (e.g. \"foo.xml\").\n */\n scrollResetKey?: string;\n\n /**\n * Optional card shown on the left side of the header actions area.\n * Supports size-dependent rendering via {@link ResponsiveCardRef}.\n */\n headerLeftCard?: ResponsiveCardRef;\n\n /**\n * Optional card shown on the right side of the header actions area.\n * Supports size-dependent rendering via {@link ResponsiveCardRef}.\n */\n headerRightCard?: ResponsiveCardRef;\n\n /** Additional style overrides passed through to the component. */\n style?: S;\n\n /** Additional CSS class names applied to the outermost wrapper. */\n className?: string;\n};\n\nexport type NavLink = {\n id: string;\n title?: string;\n};\n\nexport type PageWithNavbarNavigateToEvent = {id: string};\n\nexport type PageWithNavbarEvents = {\n onNavigateTo: PageWithNavbarNavigateToEvent;\n};\n",
42
+ "type": "registry:component",
43
+ "target": "src/cards/pageWithNavbar/pageWithNavbar.type.ts"
44
+ }
45
+ ]
46
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "pasteTarget",
4
+ "type": "registry:component",
5
+ "description": "Pihanga pasteTarget card component",
6
+ "dependencies": [
7
+ "class-variance-authority@^0.7.1",
8
+ "clsx@^2.1.1",
9
+ "radix-ui@^1.4.3"
10
+ ],
11
+ "registryDependencies": [
12
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
13
+ "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-ui-extras.json"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "cards/pasteTarget/index.ts",
18
+ "content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {PasteTargetComponent} from \"./pasteTarget.component\";\nimport {PASTE_TARGET_ACTION, PASTE_TARGET_CARD} from \"./pasteTarget.types\";\n\nexport * from \"./pasteTarget.types\";\n\nregisterCardComponent({\n name: PASTE_TARGET_CARD,\n component: PasteTargetComponent,\n events: actionTypesToEvents(PASTE_TARGET_ACTION),\n});\n",
19
+ "type": "registry:component",
20
+ "target": "src/cards/pasteTarget/index.ts"
21
+ },
22
+ {
23
+ "path": "cards/pasteTarget/pasteTarget.component.tsx",
24
+ "content": "import {\n DEF_ERROR_COLOR,\n DEF_EVENT_DURATION_SECONDS,\n DEF_HEIGHT,\n DEF_OVERLAY_OPACITY,\n DEF_PASTE_FIRST_REMINDER,\n DEF_SUCCESS_COLOR,\n PasteItem,\n PasteTargetEvents,\n PasteTargetProps,\n} from \"@/cards/pasteTarget/pasteTarget.types\";\nimport {PiCardProps} from \"@pihanga2/core\";\nimport React, {useRef, useState} from \"react\";\n\nimport \"./pasteTarget.css\";\nimport clsx from \"clsx\";\nimport {Button} from \"@/components/ui/button\";\n\n// /* 3. Stretch the child to cover the parent entirely */\n// top: 0,\n// left: 0,\n// right: 0,\n// bottom: 0,\n\n// /* 4. Ensure the child is visually stacked above the parent's content */\n// z-index: 10,\n\n// /* Optional: Make the overlay visible (e.g., semi-transparent black) */\n// overlayOpacity: \"rgba(0, 0, 0, 0.5)\";\n// }\n\nexport const PasteTargetComponent = (\n props: PiCardProps<PasteTargetProps, PasteTargetEvents>,\n): React.ReactNode => {\n const {\n title, // = \"Paste content right here\",\n description,\n pasteFirstReminder = DEF_PASTE_FIRST_REMINDER,\n withUpload,\n\n height = DEF_HEIGHT,\n\n onPastedContent,\n\n successColor = DEF_SUCCESS_COLOR,\n errorColor = DEF_ERROR_COLOR,\n eventDurationSeconds = DEF_EVENT_DURATION_SECONDS,\n eventOpacity = DEF_OVERLAY_OPACITY,\n cardName,\n className,\n _cls,\n } = props;\n const [overlayOpacity, setOverlayOpacity] = useState<number>(0);\n const [overlayBackground, setOverlayBackground] = useState<string>(\"gray\");\n const timerRef = React.useRef<number | null>(null);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const [isFocused, setIsFocused] = useState<boolean>(false);\n\n const overlayStyle: React.CSSProperties = {\n backgroundColor: overlayBackground,\n opacity: overlayOpacity,\n };\n\n const handlePaste = (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n }\n\n event.preventDefault();\n const clipboardData =\n event.clipboardData ||\n (window as Window & {clipboardData?: DataTransfer}).clipboardData;\n\n if (clipboardData) {\n const pItems = clipboardData.items;\n const items: PasteItem[] = [];\n for (let i = 0; i < pItems.length; i++) {\n const pi = pItems[i];\n const mimeType = pi.type;\n const content = clipboardData.getData(pi.type);\n if (!(content == \"\" || content == \"\\n\")) {\n items.push({mimeType, content});\n }\n }\n onPastedContent({items});\n setOverlayBackground(successColor);\n } else {\n setOverlayBackground(errorColor);\n }\n setOverlayOpacity(eventOpacity);\n if (textareaRef.current) textareaRef.current.blur();\n\n // Set a timeout to switch the color back after 'n' seconds\n const newTimerId = setTimeout(() => {\n setOverlayOpacity(0);\n timerRef.current = null; // Clear the ref after the timeout executes\n }, eventDurationSeconds * 1000); // Convert seconds to milliseconds\n\n // Save the new timer ID to the ref (unknown necessary type casting)\n timerRef.current = newTimerId as unknown as number;\n };\n\n // Cleanup function to clear the timer when the component unmounts\n React.useEffect(() => {\n return () => {\n const ref = timerRef.current;\n if (ref) {\n clearTimeout(ref);\n }\n };\n }, []); // runs only on mount/unmount\n\n function renderPasteHandler() {\n return (\n <textarea\n onPaste={handlePaste}\n onFocus={() => {\n console.log(\"...focused\");\n setIsFocused(true);\n }}\n onBlur={() => setIsFocused(false)}\n style={overlayStyle}\n // 🔑 CRUCIAL: Auto-focus the element so it's ready to receive the paste event.\n // autoFocus\n readOnly\n ref={textareaRef}\n />\n );\n }\n\n function onUpload(ev: React.MouseEvent<HTMLButtonElement>) {\n console.log(\"Clicked!!!\");\n ev.stopPropagation();\n ev.preventDefault();\n }\n\n function renderTitle() {\n if (title) {\n return <h2 className=\"paste-target-msg-title\">{title}</h2>;\n } else {\n return (\n <div className=\"paste-target-msg-title\">\n Paste\n {!isFocused && withUpload && (\n <>\n &nbsp;or&nbsp;\n <Button\n onClick={onUpload}\n style={{position: \"relative\", zIndex: 30}}\n >\n Upload\n </Button>\n </>\n )}\n &nbsp;Specification here\n </div>\n );\n }\n }\n\n const cn = _cls(\"root\", className || \"pihanga-paste-target\");\n\n return (\n <div\n className={clsx(cn, isFocused && \"pihanga-paste-target-focused\")}\n data-pihanga={cardName}\n style={{position: \"relative\", height}}\n >\n <div className=\"paste-target-label\">\n {renderTitle()}\n {description && (\n <span className=\"paste-target-msg-desc\">{description}</span>\n )}\n <div className=\"paste-target-focus-reminder\">{pasteFirstReminder}</div>\n </div>\n {renderPasteHandler()}\n </div>\n );\n};\n",
25
+ "type": "registry:component",
26
+ "target": "src/cards/pasteTarget/pasteTarget.component.tsx"
27
+ },
28
+ {
29
+ "path": "cards/pasteTarget/pasteTarget.css",
30
+ "content": ".pihanga-paste-target {\n\n border: dashed 2px #999;\n\n .paste-target-msg-title {\n font-size: 1.2em\n }\n\n .paste-target-label {\n position: absolute;\n\n /* Stretch the child to cover the parent entirely */\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n text-align: center;\n width: 100%;\n }\n\n textarea {\n position: absolute;\n\n /* Stretch the child to cover the parent entirely */\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n\n /* Ensure the child is visually stacked above the parent's content */\n z-index: 10;\n\n cursor: pointer;\n }\n\n textarea:focus {\n cursor: copy;\n\n /* Remove the default focus outline for a cleaner look (optional but common) */\n outline: none;\n }\n}\n\n.pihanga-paste-target-focused {\n /* Change the border color to highlight the active element */\n border-color: #007bff;\n border-width: 3px;\n\n .paste-target-focus-reminder {\n display: none\n }\n}",
31
+ "type": "registry:component",
32
+ "target": "src/cards/pasteTarget/pasteTarget.css"
33
+ },
34
+ {
35
+ "path": "cards/pasteTarget/pasteTarget.types.ts",
36
+ "content": " \nimport {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PASTE_TARGET_CARD = \"paste-target\";\nexport const PasteTarget = createCardDeclaration<\n PasteTargetProps,\n PasteTargetEvents\n>(PASTE_TARGET_CARD);\n\nexport const PASTE_TARGET_ACTION = registerActions(PASTE_TARGET_CARD, [\n \"pasted_content\",\n \"error\",\n]);\n\nexport const onContentPasted = createOnAction<CloseEvent>(\n PASTE_TARGET_ACTION.PASTED_CONTENT\n);\n\nexport const onPasteError = createOnAction<PasteTargetErrorEvent>(\n PASTE_TARGET_ACTION.ERROR\n);\n\nexport const DEF_HEIGHT = \"120px\";\nexport const DEF_SUCCESS_COLOR = \"lightgreen\";\nexport const DEF_ERROR_COLOR = \"lightred\";\nexport const DEF_EVENT_DURATION_SECONDS = 1;\nexport const DEF_OVERLAY_OPACITY = 0.5;\n\nexport const DEF_PASTE_FIRST_REMINDER =\n \"(Before pasting, click this this box to get the focus)\";\n\nexport type PasteTargetProps = {\n fileTypes?: string[];\n title?: string;\n description?: string;\n pasteFirstReminder?: string;\n withUpload?: boolean;\n\n height?: string | number;\n\n successColor?: string;\n errorColor?: string;\n eventDurationSeconds?: number; // How long to show success/error overlay color\n eventOpacity?: number; // Opacity of overlay on paste event\n\n className?: string;\n};\n\nexport const DEF_PASTE_TARGET_FILE_TYPES = [\"JPG\", \"PNG\", \"GIF\"];\n\nexport type PasteItem = {\n mimeType: string;\n content: string;\n};\n\nexport type PasteTargetPastedEvent = {\n items: PasteItem[];\n};\n\nexport type PasteTargetErrorEvent = {\n error: string;\n};\n\nexport type PasteTargetEvents = {\n onPastedContent: PasteTargetPastedEvent;\n onError: PasteTargetErrorEvent;\n};\n",
37
+ "type": "registry:component",
38
+ "target": "src/cards/pasteTarget/pasteTarget.types.ts"
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "pihanga-base",
4
+ "type": "registry:component",
5
+ "description": "Pihanga core runtime dependency — no source files; installing it triggers automatic `npm install @pihanga2/core`.",
6
+ "dependencies": [
7
+ "@pihanga2/core"
8
+ ],
9
+ "registryDependencies": [],
10
+ "files": []
11
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "pihanga-cards-icons",
4
+ "type": "registry:component",
5
+ "description": "Pihanga icon registry — registerIcon / getIcon helpers",
6
+ "dependencies": [],
7
+ "registryDependencies": [],
8
+ "files": [
9
+ {
10
+ "path": "cards/icons.ts",
11
+ "content": "import React from \"react\";\n\ntype G = {\n name2icon: {[name: string]: React.ElementType};\n};\n\ndeclare global {\n interface Window {\n _PihangaIcons: G;\n }\n}\n\nwindow._PihangaIcons = window._PihangaIcons || {\n name2icon: {},\n};\n\nconst name2icon = window._PihangaIcons.name2icon;\n\n\n\nexport function registerIcon(name: string, el: React.ElementType): string {\n if (name2icon[name] !== undefined) {\n throw new Error(`icon '${name}' already registered`);\n }\n name2icon[name] = el;\n return name;\n}\n\nexport function getIconElement(name: string): React.ElementType | undefined {\n return name2icon[name];\n}\n\n// can't figure out what the proper return type is, SVvgIconT does\n// NOT work with <IconButton>\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getIcon(name: string, props?: any): React.ReactNode {\n const icon = name2icon[name];\n if (!icon) return null;\n const el = React.createElement(icon, props);\n return el;\n}\n",
12
+ "type": "registry:component",
13
+ "target": "src/cards/icons.ts"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "pihanga-cards-types",
4
+ "type": "registry:component",
5
+ "description": "Shared Pihanga card type definitions (ScreenSize, VariantT, SizeT, …)",
6
+ "dependencies": [],
7
+ "registryDependencies": [],
8
+ "files": [
9
+ {
10
+ "path": "cards/types.ts",
11
+ "content": "/**\n * Screen size breakpoints matching Tailwind CSS defaults.\n *\n * Used with {@link SizeMap} to render different cards (or none at all)\n * depending on the viewport width.\n */\nexport enum ScreenSize {\n XS = \"xs\", // default / < 640px\n SM = \"sm\", // ≥ 640px\n MD = \"md\", // ≥ 768px\n LG = \"lg\", // ≥ 1024px\n XL = \"xl\", // ≥ 1280px\n XXL = \"2xl\", // ≥ 1536px\n}\n\n/**\n * Ascending order of breakpoints — used for cascade resolution.\n * @internal\n */\nexport const SCREEN_SIZE_ORDER: ScreenSize[] = [\n ScreenSize.XS,\n ScreenSize.SM,\n ScreenSize.MD,\n ScreenSize.LG,\n ScreenSize.XL,\n ScreenSize.XXL,\n];\n\n/**\n * Minimum viewport widths (px) for each breakpoint.\n * @internal\n */\nexport const SCREEN_SIZE_WIDTHS: Record<ScreenSize, number> = {\n [ScreenSize.XS]: 0,\n [ScreenSize.SM]: 640,\n [ScreenSize.MD]: 768,\n [ScreenSize.LG]: 1024,\n [ScreenSize.XL]: 1280,\n [ScreenSize.XXL]: 1536,\n};\n\n/**\n * Map a value {@link T} to specific screen-size breakpoints.\n *\n * Resolution follows a **mobile-first cascade**: the entry with the\n * largest breakpoint that is ≤ the current viewport size wins.\n *\n * ```ts\n * // Show \"desktopCard\" on md and above, nothing on xs/sm:\n * { [ScreenSize.MD]: \"desktopCard\" }\n *\n * // Show a different card per breakpoint:\n * { [ScreenSize.XS]: \"mobileCard\", [ScreenSize.MD]: \"desktopCard\" }\n *\n * // Explicitly hide on md and above:\n * { [ScreenSize.XS]: \"card\", [ScreenSize.MD]: null }\n * ```\n *\n * Set `default` as a final fallback when no breakpoint entry matches.\n */\nexport type SizeMap<T> = Partial<Record<ScreenSize, T | null>> & {\n default?: T | null;\n};\n\n// ---------------------------------------------------------------------------\n// Shadcn / Tailwind-native shared primitive types\n//\n// These types replace what was previously imported from `@pihanga2/cards` and\n// are expressed in terms of Shadcn's design system and Tailwind utility tokens.\n// ---------------------------------------------------------------------------\n\n/**\n * Shadcn button / badge visual variant.\n *\n * Maps directly to the `variant` prop accepted by `@/components/ui/button`\n * and `@/components/ui/badge`.\n */\nexport type VariantT =\n | \"default\"\n | \"destructive\"\n | \"outline\"\n | \"secondary\"\n | \"ghost\"\n | \"link\";\n\n/**\n * Shadcn component size token.\n *\n * Maps directly to the `size` prop accepted by `@/components/ui/button`.\n */\nexport type SizeT = \"default\" | \"sm\" | \"lg\" | \"icon\";\n\n/**\n * Tailwind colour token (e.g. `\"red\"`, `\"emerald\"`, `\"sky\"`) or an arbitrary\n * CSS colour string. Kept as `string` so consumers can pass any Tailwind\n * palette key without being locked to a finite union.\n */\nexport type ColorT = string;\n\n/**\n * A decorator displayed before or after a button / menu-item label.\n *\n * Expressed as a registered icon name (resolved at render time via the icon\n * registry) or an arbitrary Tailwind-class string for inline SVG icons.\n */\nexport type DecoratorT = string;\n",
12
+ "type": "registry:component",
13
+ "target": "src/cards/types.ts"
14
+ }
15
+ ]
16
+ }