@ds-mo/ui 0.2.4 → 0.4.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 (356) hide show
  1. package/dist/components/ds-accordion.d.ts +11 -0
  2. package/dist/components/ds-accordion.js +2 -0
  3. package/dist/components/ds-accordion.js.map +1 -0
  4. package/dist/components/ds-badge.d.ts +11 -0
  5. package/dist/components/ds-badge.js +2 -0
  6. package/dist/components/ds-badge.js.map +1 -0
  7. package/dist/components/ds-banner.d.ts +11 -0
  8. package/dist/components/ds-banner.js +2 -0
  9. package/dist/components/ds-banner.js.map +1 -0
  10. package/dist/components/ds-breadcrumb.d.ts +11 -0
  11. package/dist/components/ds-breadcrumb.js +2 -0
  12. package/dist/components/ds-breadcrumb.js.map +1 -0
  13. package/dist/components/ds-button.d.ts +11 -0
  14. package/dist/components/ds-button.js +2 -0
  15. package/dist/components/ds-button.js.map +1 -0
  16. package/dist/components/ds-card.d.ts +11 -0
  17. package/dist/components/ds-card.js +2 -0
  18. package/dist/components/ds-card.js.map +1 -0
  19. package/dist/components/ds-checkbox.d.ts +11 -0
  20. package/dist/components/ds-checkbox.js +2 -0
  21. package/dist/components/ds-checkbox.js.map +1 -0
  22. package/dist/components/ds-divider.d.ts +11 -0
  23. package/dist/components/ds-divider.js +2 -0
  24. package/dist/components/ds-divider.js.map +1 -0
  25. package/dist/components/ds-empty-state.d.ts +11 -0
  26. package/dist/components/ds-empty-state.js +2 -0
  27. package/dist/components/ds-empty-state.js.map +1 -0
  28. package/dist/components/ds-fade.d.ts +11 -0
  29. package/dist/components/ds-fade.js +2 -0
  30. package/dist/components/ds-fade.js.map +1 -0
  31. package/dist/components/ds-field.d.ts +11 -0
  32. package/dist/components/ds-field.js +2 -0
  33. package/dist/components/ds-field.js.map +1 -0
  34. package/dist/components/ds-header.d.ts +11 -0
  35. package/dist/components/ds-header.js +2 -0
  36. package/dist/components/ds-header.js.map +1 -0
  37. package/dist/components/ds-input.d.ts +11 -0
  38. package/dist/components/ds-input.js +2 -0
  39. package/dist/components/ds-input.js.map +1 -0
  40. package/dist/components/ds-loader.d.ts +11 -0
  41. package/dist/components/ds-loader.js +2 -0
  42. package/dist/components/ds-loader.js.map +1 -0
  43. package/dist/components/ds-menu.d.ts +11 -0
  44. package/dist/components/ds-menu.js +2 -0
  45. package/dist/components/ds-menu.js.map +1 -0
  46. package/dist/components/ds-modal.d.ts +11 -0
  47. package/dist/components/ds-modal.js +2 -0
  48. package/dist/components/ds-modal.js.map +1 -0
  49. package/dist/components/ds-pagination.d.ts +11 -0
  50. package/dist/components/ds-pagination.js +2 -0
  51. package/dist/components/ds-pagination.js.map +1 -0
  52. package/dist/components/ds-radio-group.d.ts +11 -0
  53. package/dist/components/ds-radio-group.js +2 -0
  54. package/dist/components/ds-radio-group.js.map +1 -0
  55. package/dist/components/ds-scrollbar.d.ts +11 -0
  56. package/dist/components/ds-scrollbar.js +2 -0
  57. package/dist/components/ds-scrollbar.js.map +1 -0
  58. package/dist/components/ds-sidebar.d.ts +11 -0
  59. package/dist/components/ds-sidebar.js +2 -0
  60. package/dist/components/ds-sidebar.js.map +1 -0
  61. package/dist/components/ds-skeleton.d.ts +11 -0
  62. package/dist/components/ds-skeleton.js +2 -0
  63. package/dist/components/ds-skeleton.js.map +1 -0
  64. package/dist/components/ds-slider.d.ts +11 -0
  65. package/dist/components/ds-slider.js +2 -0
  66. package/dist/components/ds-slider.js.map +1 -0
  67. package/dist/components/ds-surface.d.ts +11 -0
  68. package/dist/components/ds-surface.js +2 -0
  69. package/dist/components/ds-surface.js.map +1 -0
  70. package/dist/components/ds-tab-group.d.ts +11 -0
  71. package/dist/components/ds-tab-group.js +2 -0
  72. package/dist/components/ds-tab-group.js.map +1 -0
  73. package/dist/components/ds-table.d.ts +11 -0
  74. package/dist/components/ds-table.js +2 -0
  75. package/dist/components/ds-table.js.map +1 -0
  76. package/dist/components/ds-tag.d.ts +11 -0
  77. package/dist/components/ds-tag.js +2 -0
  78. package/dist/components/ds-tag.js.map +1 -0
  79. package/dist/components/ds-text.d.ts +11 -0
  80. package/dist/components/ds-text.js +2 -0
  81. package/dist/components/ds-text.js.map +1 -0
  82. package/dist/components/ds-toast-provider.d.ts +11 -0
  83. package/dist/components/ds-toast-provider.js +2 -0
  84. package/dist/components/ds-toast-provider.js.map +1 -0
  85. package/dist/components/ds-toggle.d.ts +11 -0
  86. package/dist/components/ds-toggle.js +2 -0
  87. package/dist/components/ds-toggle.js.map +1 -0
  88. package/dist/components/ds-tooltip.d.ts +11 -0
  89. package/dist/components/ds-tooltip.js +2 -0
  90. package/dist/components/ds-tooltip.js.map +1 -0
  91. package/dist/components/index.d.ts +33 -0
  92. package/dist/components/index.js +2 -0
  93. package/dist/components/index.js.map +1 -0
  94. package/dist/components/p-BcuUR5Ao.js +2 -0
  95. package/dist/components/p-BcuUR5Ao.js.map +1 -0
  96. package/dist/types/.stencil/Accordion/Accordion.d.ts +28 -0
  97. package/dist/types/.stencil/Badge/Badge.d.ts +8 -0
  98. package/dist/types/.stencil/Badge/index.d.ts +2 -0
  99. package/dist/types/.stencil/Banner/Banner.d.ts +29 -0
  100. package/dist/types/.stencil/Breadcrumb/Breadcrumb.d.ts +16 -0
  101. package/dist/types/.stencil/Button/Button.d.ts +38 -0
  102. package/dist/types/.stencil/Button/index.d.ts +2 -0
  103. package/dist/types/.stencil/Card/Card.d.ts +8 -0
  104. package/dist/types/.stencil/Checkbox/Checkbox.d.ts +13 -0
  105. package/dist/types/.stencil/Checkbox/index.d.ts +2 -0
  106. package/dist/types/.stencil/Divider/Divider.d.ts +7 -0
  107. package/dist/types/.stencil/Divider/index.d.ts +2 -0
  108. package/dist/types/.stencil/EmptyState/EmptyState.d.ts +7 -0
  109. package/dist/types/.stencil/Fade/Fade.d.ts +10 -0
  110. package/dist/types/.stencil/Fade/index.d.ts +2 -0
  111. package/dist/types/.stencil/Field/Field.d.ts +8 -0
  112. package/dist/types/.stencil/Header/Header.d.ts +7 -0
  113. package/dist/types/.stencil/Input/Input.d.ts +26 -0
  114. package/dist/types/.stencil/Input/index.d.ts +2 -0
  115. package/dist/types/.stencil/Loader/Loader.d.ts +12 -0
  116. package/dist/types/.stencil/Loader/index.d.ts +2 -0
  117. package/dist/types/.stencil/Menu/Menu.d.ts +60 -0
  118. package/dist/types/.stencil/Modal/Modal.d.ts +31 -0
  119. package/dist/types/.stencil/Pagination/Pagination.d.ts +11 -0
  120. package/dist/types/.stencil/RadioGroup/RadioGroup.d.ts +21 -0
  121. package/dist/types/.stencil/RadioGroup/index.d.ts +2 -0
  122. package/dist/types/.stencil/Scrollbar/Scrollbar.d.ts +27 -0
  123. package/dist/types/.stencil/Sidebar/Sidebar.d.ts +29 -0
  124. package/dist/types/.stencil/Skeleton/Skeleton.d.ts +16 -0
  125. package/dist/types/.stencil/Skeleton/index.d.ts +2 -0
  126. package/dist/types/.stencil/Slider/Slider.d.ts +18 -0
  127. package/dist/types/.stencil/Slider/index.d.ts +2 -0
  128. package/dist/types/.stencil/Surface/Surface.d.ts +22 -0
  129. package/dist/types/.stencil/TabGroup/TabGroup.d.ts +29 -0
  130. package/dist/types/.stencil/Table/Table.d.ts +50 -0
  131. package/dist/types/.stencil/Tag/Tag.d.ts +32 -0
  132. package/dist/types/.stencil/Tag/index.d.ts +2 -0
  133. package/dist/types/.stencil/Text/Text.d.ts +22 -0
  134. package/dist/types/.stencil/Text/index.d.ts +2 -0
  135. package/dist/types/.stencil/Toast/Toast.d.ts +15 -0
  136. package/dist/types/.stencil/Toast/toast-service.d.ts +24 -0
  137. package/dist/types/.stencil/Toggle/Toggle.d.ts +10 -0
  138. package/dist/types/.stencil/Toggle/index.d.ts +2 -0
  139. package/dist/types/.stencil/Tooltip/Tooltip.d.ts +36 -0
  140. package/dist/types/components.d.ts +2024 -0
  141. package/dist/types/stencil-public-runtime.d.ts +1860 -0
  142. package/package.json +33 -29
  143. package/src/angular/angular-component-lib/utils.ts +65 -0
  144. package/src/angular/index.ts +35 -0
  145. package/src/angular/proxies.ts +819 -0
  146. package/dist/index.css +0 -2
  147. package/dist/index.d.ts +0 -969
  148. package/dist/index.js +0 -2492
  149. package/dist/index.js.map +0 -1
  150. package/dist/r/accordion.json +0 -79
  151. package/dist/r/badge.json +0 -69
  152. package/dist/r/banner.json +0 -90
  153. package/dist/r/breadcrumb.json +0 -70
  154. package/dist/r/button-group.json +0 -65
  155. package/dist/r/button.json +0 -137
  156. package/dist/r/card.json +0 -82
  157. package/dist/r/checkbox.json +0 -91
  158. package/dist/r/divider.json +0 -57
  159. package/dist/r/empty-state.json +0 -69
  160. package/dist/r/error-boundary.json +0 -66
  161. package/dist/r/fade.json +0 -65
  162. package/dist/r/field.json +0 -71
  163. package/dist/r/header.json +0 -67
  164. package/dist/r/input.json +0 -77
  165. package/dist/r/loader.json +0 -54
  166. package/dist/r/menu.json +0 -130
  167. package/dist/r/modal.json +0 -93
  168. package/dist/r/pagination.json +0 -80
  169. package/dist/r/radio.json +0 -85
  170. package/dist/r/registry.json +0 -1600
  171. package/dist/r/scrollbar.json +0 -68
  172. package/dist/r/select.json +0 -93
  173. package/dist/r/sidebar.json +0 -105
  174. package/dist/r/skeleton.json +0 -72
  175. package/dist/r/slider.json +0 -87
  176. package/dist/r/surface.json +0 -97
  177. package/dist/r/tab-group.json +0 -72
  178. package/dist/r/tab.json +0 -71
  179. package/dist/r/table.json +0 -110
  180. package/dist/r/tag.json +0 -110
  181. package/dist/r/text.json +0 -94
  182. package/dist/r/toast.json +0 -77
  183. package/dist/r/toggle-button-group.json +0 -65
  184. package/dist/r/toggle-button.json +0 -94
  185. package/dist/r/toggle.json +0 -65
  186. package/dist/r/tooltip.json +0 -86
  187. package/src/components/Accordion/Accordion.module.css +0 -66
  188. package/src/components/Accordion/Accordion.stories.tsx +0 -95
  189. package/src/components/Accordion/Accordion.tsx +0 -122
  190. package/src/components/Accordion/index.ts +0 -2
  191. package/src/components/Badge/Badge.module.css +0 -6
  192. package/src/components/Badge/Badge.stories.tsx +0 -99
  193. package/src/components/Badge/Badge.tsx +0 -23
  194. package/src/components/Badge/index.ts +0 -2
  195. package/src/components/Banner/Banner.module.css +0 -67
  196. package/src/components/Banner/Banner.stories.tsx +0 -81
  197. package/src/components/Banner/Banner.tsx +0 -109
  198. package/src/components/Banner/index.ts +0 -2
  199. package/src/components/Breadcrumb/Breadcrumb.module.css +0 -46
  200. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +0 -72
  201. package/src/components/Breadcrumb/Breadcrumb.tsx +0 -68
  202. package/src/components/Breadcrumb/index.ts +0 -2
  203. package/src/components/Button/Button.mdx +0 -143
  204. package/src/components/Button/Button.module.css +0 -343
  205. package/src/components/Button/Button.stories.tsx +0 -575
  206. package/src/components/Button/Button.tsx +0 -201
  207. package/src/components/Button/index.ts +0 -10
  208. package/src/components/ButtonGroup/ButtonGroup.module.css +0 -157
  209. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +0 -384
  210. package/src/components/ButtonGroup/ButtonGroup.tsx +0 -122
  211. package/src/components/ButtonGroup/index.ts +0 -2
  212. package/src/components/Card/Card.module.css +0 -36
  213. package/src/components/Card/Card.stories.tsx +0 -78
  214. package/src/components/Card/Card.tsx +0 -53
  215. package/src/components/Card/index.ts +0 -2
  216. package/src/components/Checkbox/Checkbox.module.css +0 -47
  217. package/src/components/Checkbox/Checkbox.stories.tsx +0 -129
  218. package/src/components/Checkbox/Checkbox.tsx +0 -68
  219. package/src/components/Checkbox/index.ts +0 -2
  220. package/src/components/Divider/Divider.module.css +0 -19
  221. package/src/components/Divider/Divider.stories.tsx +0 -55
  222. package/src/components/Divider/Divider.tsx +0 -28
  223. package/src/components/Divider/index.ts +0 -2
  224. package/src/components/EmptyState/EmptyState.module.css +0 -8
  225. package/src/components/EmptyState/EmptyState.stories.tsx +0 -53
  226. package/src/components/EmptyState/EmptyState.tsx +0 -31
  227. package/src/components/EmptyState/index.ts +0 -2
  228. package/src/components/ErrorBoundary/ErrorBoundary.module.css +0 -18
  229. package/src/components/ErrorBoundary/ErrorBoundary.stories.tsx +0 -38
  230. package/src/components/ErrorBoundary/ErrorBoundary.tsx +0 -50
  231. package/src/components/ErrorBoundary/index.ts +0 -2
  232. package/src/components/Fade/Fade.module.css +0 -31
  233. package/src/components/Fade/Fade.stories.tsx +0 -35
  234. package/src/components/Fade/Fade.tsx +0 -33
  235. package/src/components/Fade/index.ts +0 -2
  236. package/src/components/Field/Field.module.css +0 -5
  237. package/src/components/Field/Field.stories.tsx +0 -111
  238. package/src/components/Field/Field.tsx +0 -22
  239. package/src/components/Field/index.ts +0 -2
  240. package/src/components/Header/Header.module.css +0 -33
  241. package/src/components/Header/Header.stories.tsx +0 -52
  242. package/src/components/Header/Header.tsx +0 -42
  243. package/src/components/Header/index.ts +0 -2
  244. package/src/components/Input/Input.module.css +0 -71
  245. package/src/components/Input/Input.stories.tsx +0 -135
  246. package/src/components/Input/Input.tsx +0 -76
  247. package/src/components/Input/index.ts +0 -2
  248. package/src/components/LabelWrap/LabelWrap.module.css +0 -34
  249. package/src/components/LabelWrap/LabelWrap.tsx +0 -32
  250. package/src/components/LabelWrap/index.ts +0 -1
  251. package/src/components/Loader/Loader.module.css +0 -9
  252. package/src/components/Loader/Loader.stories.tsx +0 -44
  253. package/src/components/Loader/Loader.tsx +0 -43
  254. package/src/components/Loader/index.ts +0 -2
  255. package/src/components/Menu/DestructiveMenuItem.module.css +0 -57
  256. package/src/components/Menu/DestructiveMenuItem.tsx +0 -64
  257. package/src/components/Menu/Menu.module.css +0 -49
  258. package/src/components/Menu/Menu.stories.tsx +0 -129
  259. package/src/components/Menu/Menu.tsx +0 -269
  260. package/src/components/Menu/MenuItem.module.css +0 -84
  261. package/src/components/Menu/MenuItem.tsx +0 -101
  262. package/src/components/Menu/index.ts +0 -6
  263. package/src/components/Modal/Modal.module.css +0 -67
  264. package/src/components/Modal/Modal.stories.tsx +0 -71
  265. package/src/components/Modal/Modal.tsx +0 -122
  266. package/src/components/Modal/index.ts +0 -2
  267. package/src/components/Pagination/Pagination.module.css +0 -64
  268. package/src/components/Pagination/Pagination.stories.tsx +0 -51
  269. package/src/components/Pagination/Pagination.tsx +0 -103
  270. package/src/components/Pagination/index.ts +0 -2
  271. package/src/components/Radio/Radio.module.css +0 -61
  272. package/src/components/Radio/Radio.stories.tsx +0 -101
  273. package/src/components/Radio/Radio.tsx +0 -108
  274. package/src/components/Radio/index.ts +0 -2
  275. package/src/components/Scrollbar/Scrollbar.module.css +0 -81
  276. package/src/components/Scrollbar/Scrollbar.stories.tsx +0 -46
  277. package/src/components/Scrollbar/Scrollbar.tsx +0 -187
  278. package/src/components/Scrollbar/index.ts +0 -2
  279. package/src/components/Select/Select.module.css +0 -31
  280. package/src/components/Select/Select.stories.tsx +0 -59
  281. package/src/components/Select/Select.tsx +0 -92
  282. package/src/components/Select/index.ts +0 -2
  283. package/src/components/Sidebar/Sidebar.module.css +0 -161
  284. package/src/components/Sidebar/Sidebar.stories.tsx +0 -125
  285. package/src/components/Sidebar/Sidebar.tsx +0 -129
  286. package/src/components/Sidebar/SidebarItem.tsx +0 -57
  287. package/src/components/Sidebar/SidebarSection.tsx +0 -42
  288. package/src/components/Sidebar/index.ts +0 -6
  289. package/src/components/Skeleton/Skeleton.module.css +0 -53
  290. package/src/components/Skeleton/Skeleton.stories.tsx +0 -89
  291. package/src/components/Skeleton/Skeleton.tsx +0 -66
  292. package/src/components/Skeleton/index.ts +0 -2
  293. package/src/components/Slider/Slider.module.css +0 -62
  294. package/src/components/Slider/Slider.stories.tsx +0 -120
  295. package/src/components/Slider/Slider.tsx +0 -74
  296. package/src/components/Slider/index.ts +0 -2
  297. package/src/components/Surface/Surface.module.css +0 -222
  298. package/src/components/Surface/Surface.stories.tsx +0 -213
  299. package/src/components/Surface/Surface.tsx +0 -219
  300. package/src/components/Surface/index.ts +0 -12
  301. package/src/components/Tab/Tab.module.css +0 -66
  302. package/src/components/Tab/Tab.stories.tsx +0 -103
  303. package/src/components/Tab/Tab.tsx +0 -41
  304. package/src/components/Tab/index.ts +0 -2
  305. package/src/components/TabGroup/TabGroup.module.css +0 -26
  306. package/src/components/TabGroup/TabGroup.stories.tsx +0 -60
  307. package/src/components/TabGroup/TabGroup.tsx +0 -83
  308. package/src/components/TabGroup/index.ts +0 -2
  309. package/src/components/Table/Table.module.css +0 -130
  310. package/src/components/Table/Table.stories.tsx +0 -111
  311. package/src/components/Table/Table.tsx +0 -226
  312. package/src/components/Table/index.ts +0 -11
  313. package/src/components/Tag/Tag.module.css +0 -270
  314. package/src/components/Tag/Tag.stories.tsx +0 -399
  315. package/src/components/Tag/Tag.tsx +0 -162
  316. package/src/components/Tag/index.ts +0 -2
  317. package/src/components/Text/Text.module.css +0 -166
  318. package/src/components/Text/Text.stories.tsx +0 -212
  319. package/src/components/Text/Text.tsx +0 -181
  320. package/src/components/Text/index.ts +0 -13
  321. package/src/components/Toast/Toast.module.css +0 -91
  322. package/src/components/Toast/Toast.stories.tsx +0 -66
  323. package/src/components/Toast/Toast.tsx +0 -182
  324. package/src/components/Toast/index.ts +0 -8
  325. package/src/components/Toggle/Toggle.module.css +0 -87
  326. package/src/components/Toggle/Toggle.stories.tsx +0 -85
  327. package/src/components/Toggle/Toggle.tsx +0 -52
  328. package/src/components/Toggle/index.ts +0 -2
  329. package/src/components/ToggleButton/ToggleButton.module.css +0 -172
  330. package/src/components/ToggleButton/ToggleButton.stories.tsx +0 -299
  331. package/src/components/ToggleButton/ToggleButton.tsx +0 -118
  332. package/src/components/ToggleButton/index.ts +0 -2
  333. package/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css +0 -163
  334. package/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx +0 -341
  335. package/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx +0 -111
  336. package/src/components/ToggleButtonGroup/index.ts +0 -2
  337. package/src/components/Tooltip/Tooltip.module.css +0 -45
  338. package/src/components/Tooltip/Tooltip.stories.tsx +0 -46
  339. package/src/components/Tooltip/Tooltip.tsx +0 -224
  340. package/src/components/Tooltip/index.ts +0 -2
  341. package/src/css-modules.d.ts +0 -4
  342. package/src/docs/ColorUsage.mdx +0 -195
  343. package/src/docs/ElevationUsage.mdx +0 -95
  344. package/src/docs/Introduction.mdx +0 -97
  345. package/src/docs/OpticalSizing.mdx +0 -178
  346. package/src/docs/TypographyUsage.mdx +0 -121
  347. package/src/index.ts +0 -154
  348. package/src/stories/Colors.stories.tsx +0 -528
  349. package/src/stories/Dimensions.stories.tsx +0 -200
  350. package/src/stories/Effects.stories.tsx +0 -231
  351. package/src/stories/Icons.stories.tsx +0 -159
  352. package/src/stories/Typography.stories.tsx +0 -157
  353. package/src/types/icons.ts +0 -15
  354. package/src/types/index.ts +0 -1
  355. package/src/utils/cn.ts +0 -6
  356. package/src/utils/css-tokens.ts +0 -22
package/dist/r/input.json DELETED
@@ -1,77 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "input",
4
- "title": "Input",
5
- "description": "Text input supporting text, email, tel, url, search, and password types. Search type includes auto clear button.",
6
- "type": "registry:ui",
7
- "meta": {
8
- "library": "@compomo/ui",
9
- "distribution": "npm",
10
- "storybook": "https://zainadeel.github.io/CompoMo/?path=/story/input",
11
- "consumption": {
12
- "install": "npm install @compomo/ui @ds-mo/tokens",
13
- "cssSetup": "Import tokens CSS at your app entry point: import '@ds-mo/tokens/css';\nThis provides all var(--color-*), var(--dimension-*), var(--effect-*), and var(--typography-*) custom properties.\nFor dark mode, set data-theme=\"dark\" on <html>.",
14
- "import": "import { Input } from '@compomo/ui';",
15
- "cssImport": "import '@compomo/ui/css';",
16
- "typeImport": "import type { InputProps } from '@compomo/ui';",
17
- "peerDependencies": {
18
- "required": [
19
- "react >=17",
20
- "react-dom >=17",
21
- "@ds-mo/tokens >=0.1.0"
22
- ],
23
- "optional": []
24
- }
25
- },
26
- "props": {
27
- "value": {
28
- "type": "string",
29
- "required": true
30
- },
31
- "onChange": {
32
- "type": "(e: React.ChangeEvent<HTMLInputElement>) => void",
33
- "required": true
34
- },
35
- "type": {
36
- "type": "'text' | 'email' | 'tel' | 'url' | 'search' | 'password'",
37
- "default": "'text'"
38
- },
39
- "placeholder": {
40
- "type": "string"
41
- },
42
- "suffix": {
43
- "type": "React.ReactNode"
44
- },
45
- "inactive": {
46
- "type": "boolean"
47
- },
48
- "clearIcon": {
49
- "type": "IconComponent",
50
- "description": "Custom clear icon for search type."
51
- }
52
- },
53
- "exports": [
54
- "Input"
55
- ],
56
- "types": [
57
- "InputProps"
58
- ]
59
- },
60
- "dependencies": [
61
- "@compomo/ui",
62
- "@ds-mo/tokens"
63
- ],
64
- "registryDependencies": [],
65
- "files": [
66
- {
67
- "path": "src/components/Input/Input.module.css",
68
- "content": ".wrapper {\n width: 100%;\n height: var(--dimension-size-400);\n display: flex;\n align-items: center;\n background-color: var(--color-background-primary);\n border-radius: var(--dimension-radius-075);\n box-shadow: var(--effect-elevation-depressed-sm);\n box-sizing: border-box;\n}\n\n.wrapper:focus-within {\n box-shadow: inset 0 0 0 1.25px var(--color-foreground-bold-brand);\n}\n\n.row {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n gap: var(--dimension-space-050);\n}\n\n.input {\n flex: 1;\n min-width: 0;\n height: 100%;\n padding: 0 var(--dimension-space-100);\n background: transparent;\n border: none;\n outline: none;\n font-family: inherit;\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n color: var(--color-foreground-primary);\n border-radius: var(--dimension-radius-075);\n -webkit-appearance: none;\n appearance: none;\n}\n\n.input:disabled { opacity: 0.5; cursor: not-allowed; }\n.input::placeholder { color: var(--color-foreground-secondary); }\n.input:focus::placeholder { color: var(--color-foreground-tertiary); }\n\n/* Hide native search clear */\n.input[type=\"search\"]::-webkit-search-cancel-button,\n.input[type=\"search\"]::-webkit-search-decoration { display: none; }\n\n.clearButton {\n flex-shrink: 0;\n margin-right: var(--dimension-space-050);\n padding: var(--dimension-space-050);\n border: none;\n background: none;\n cursor: pointer;\n color: var(--color-foreground-secondary);\n border-radius: var(--dimension-radius-050);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.clearButton:hover { background-color: var(--color-interaction-hover); }\n\n.suffix {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-050);\n flex-shrink: 0;\n margin-right: var(--dimension-space-050);\n}\n",
69
- "type": "registry:ui"
70
- },
71
- {
72
- "path": "src/components/Input/Input.tsx",
73
- "content": "import React, { forwardRef } from 'react';\nimport { cn } from '@/utils/cn';\nimport type { IconComponent } from '@/types/icons';\nimport styles from './Input.module.css';\n\nexport type InputType = 'text' | 'email' | 'tel' | 'url' | 'search' | 'password';\n\nexport interface InputProps {\n value: string;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n id?: string;\n placeholder?: string;\n autoFocus?: boolean;\n className?: string;\n type?: InputType;\n inactive?: boolean;\n onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;\n /** Content rendered inside the field on the right. */\n suffix?: React.ReactNode;\n /** Icon for clear button (search type). */\n clearIcon?: IconComponent;\n 'aria-label'?: string;\n}\n\nexport const Input = forwardRef<HTMLDivElement, InputProps>(\n (\n {\n value,\n onChange,\n id,\n placeholder,\n autoFocus = false,\n className,\n type = 'text',\n inactive = false,\n onKeyDown,\n suffix,\n clearIcon: ClearIcon,\n 'aria-label': ariaLabel,\n },\n ref\n ) => {\n const showClear = type === 'search' && value.length > 0 && !inactive;\n\n const handleClear = () => {\n onChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);\n };\n\n return (\n <div ref={ref} className={cn(styles.wrapper, className)}>\n <div className={styles.row}>\n <input\n type={type}\n id={id}\n value={value}\n onChange={onChange}\n onKeyDown={onKeyDown}\n placeholder={placeholder}\n className={styles.input}\n autoFocus={autoFocus}\n disabled={inactive}\n aria-label={ariaLabel}\n />\n {showClear && (\n <button type=\"button\" className={styles.clearButton} onClick={handleClear} aria-label=\"Clear\">\n {ClearIcon ? <ClearIcon size={16} /> : <span aria-hidden>&times;</span>}\n </button>\n )}\n {suffix && <div className={styles.suffix}>{suffix}</div>}\n </div>\n </div>\n );\n }\n);\n\nInput.displayName = 'Input';\n",
74
- "type": "registry:ui"
75
- }
76
- ]
77
- }
@@ -1,54 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "loader",
4
- "title": "Loader",
5
- "description": "SVG spinner that scales proportionally. Uses currentColor for theming.",
6
- "type": "registry:ui",
7
- "meta": {
8
- "library": "@compomo/ui",
9
- "distribution": "npm",
10
- "storybook": "https://zainadeel.github.io/CompoMo/?path=/story/loader",
11
- "consumption": {
12
- "install": "npm install @compomo/ui",
13
- "import": "import { Loader } from '@compomo/ui';",
14
- "cssImport": "import '@compomo/ui/css';",
15
- "typeImport": "import type { LoaderProps } from '@compomo/ui';",
16
- "peerDependencies": {
17
- "required": [
18
- "react >=17",
19
- "react-dom >=17",
20
- "@ds-mo/tokens >=0.1.0"
21
- ],
22
- "optional": []
23
- }
24
- },
25
- "props": {
26
- "size": {
27
- "type": "number",
28
- "default": "20"
29
- }
30
- },
31
- "exports": [
32
- "Loader"
33
- ],
34
- "types": [
35
- "LoaderProps"
36
- ]
37
- },
38
- "dependencies": [
39
- "@compomo/ui"
40
- ],
41
- "registryDependencies": [],
42
- "files": [
43
- {
44
- "path": "src/components/Loader/Loader.module.css",
45
- "content": ".loader {\n flex-shrink: 0;\n animation: spin 0.75s linear infinite;\n}\n\n@keyframes spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n}\n",
46
- "type": "registry:ui"
47
- },
48
- {
49
- "path": "src/components/Loader/Loader.tsx",
50
- "content": "import React from 'react';\nimport styles from './Loader.module.css';\n\nexport interface LoaderProps {\n /**\n * Render size in px. Designed at 16px (r=6, strokeWidth=1).\n * Stroke weight scales proportionally with size — same behaviour as icons.\n * Defaults to 20.\n */\n size?: number;\n className?: string;\n}\n\n/**\n * Placeholder spinner component. Will be replaced with a full Loader\n * component once motion/brand specs are finalised.\n *\n * Designed at 16×16 viewBox so it matches the icon grid:\n * - r=5 → 10px circle in a 16px box (3px optical padding each side)\n * - strokeWidth=1 at native size → scales to 1.25px at 20px, 1.5px at 24px\n */\nexport const Loader = ({ size = 20, className }: LoaderProps) => (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n className={`${styles.loader}${className ? ` ${className}` : ''}`}\n aria-hidden=\"true\"\n >\n <circle\n cx=\"8\"\n cy=\"8\"\n r=\"5\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeLinecap=\"round\"\n strokeDasharray=\"23.6 7.9\"\n />\n </svg>\n);\n\nLoader.displayName = 'Loader';\n",
51
- "type": "registry:ui"
52
- }
53
- ]
54
- }
package/dist/r/menu.json DELETED
@@ -1,130 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "menu",
4
- "title": "Menu",
5
- "description": "Positioned dropdown menu with sections, item selection styles, toggle items, and destructive hold-to-confirm items.",
6
- "type": "registry:ui",
7
- "meta": {
8
- "library": "@compomo/ui",
9
- "distribution": "npm",
10
- "storybook": "https://zainadeel.github.io/CompoMo/?path=/story/menu",
11
- "consumption": {
12
- "install": "npm install @compomo/ui @ds-mo/tokens",
13
- "cssSetup": "Import tokens CSS at your app entry point: import '@ds-mo/tokens/css';\nThis provides all var(--color-*), var(--dimension-*), var(--effect-*), and var(--typography-*) custom properties.\nFor dark mode, set data-theme=\"dark\" on <html>.",
14
- "import": "import { Menu, MenuItem, DestructiveMenuItem } from '@compomo/ui';",
15
- "cssImport": "import '@compomo/ui/css';",
16
- "typeImport": "import type { MenuProps, MenuSection, MenuItemData, MenuSide, MenuAlign, MenuItemProps, MenuItemSelectionStyle, DestructiveMenuItemProps } from '@compomo/ui';",
17
- "internalDependencies": [
18
- {
19
- "component": "Surface",
20
- "note": "Used internally by Menu. No action needed — it's bundled in @compomo/ui."
21
- },
22
- {
23
- "component": "Text",
24
- "note": "Used internally by Menu. No action needed — it's bundled in @compomo/ui."
25
- },
26
- {
27
- "component": "Toggle",
28
- "note": "Used internally by Menu. No action needed — it's bundled in @compomo/ui."
29
- }
30
- ],
31
- "peerDependencies": {
32
- "required": [
33
- "react >=17",
34
- "react-dom >=17",
35
- "@ds-mo/tokens >=0.1.0"
36
- ],
37
- "optional": []
38
- }
39
- },
40
- "props": {
41
- "isOpen": {
42
- "type": "boolean",
43
- "required": true
44
- },
45
- "onClose": {
46
- "type": "() => void",
47
- "required": true
48
- },
49
- "anchorRef": {
50
- "type": "React.RefObject<HTMLElement | null>",
51
- "required": true
52
- },
53
- "items": {
54
- "type": "MenuItemData[]",
55
- "description": "Flat item list (use items or sections, not both)."
56
- },
57
- "sections": {
58
- "type": "MenuSection[]",
59
- "description": "Grouped items with optional headers."
60
- },
61
- "side": {
62
- "type": "'top' | 'right' | 'bottom' | 'left'",
63
- "default": "'bottom'"
64
- },
65
- "align": {
66
- "type": "'start' | 'center' | 'end'",
67
- "default": "'start'"
68
- },
69
- "matchAnchorWidth": {
70
- "type": "boolean"
71
- }
72
- },
73
- "exports": [
74
- "Menu",
75
- "MenuItem",
76
- "DestructiveMenuItem"
77
- ],
78
- "types": [
79
- "MenuProps",
80
- "MenuSection",
81
- "MenuItemData",
82
- "MenuSide",
83
- "MenuAlign",
84
- "MenuItemProps",
85
- "MenuItemSelectionStyle",
86
- "DestructiveMenuItemProps"
87
- ]
88
- },
89
- "dependencies": [
90
- "@compomo/ui",
91
- "@ds-mo/tokens"
92
- ],
93
- "registryDependencies": [
94
- "surface",
95
- "text",
96
- "toggle"
97
- ],
98
- "files": [
99
- {
100
- "path": "src/components/Menu/Menu.tsx",
101
- "content": "import React, { useState, useEffect, useLayoutEffect, useRef, useMemo } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Text } from '@/components/Text';\nimport { Surface } from '@/components/Surface';\nimport { MenuItem as MenuItemComponent } from './MenuItem';\nimport { DestructiveMenuItem } from './DestructiveMenuItem';\nimport { getCssSpacePx } from '@/utils/css-tokens';\nimport type { IconComponent } from '@/types';\nimport type { MenuItemSelectionStyle } from './MenuItem';\nimport styles from './Menu.module.css';\n\nconst CLOSE_ANIMATION_MS = 220;\n\nexport type MenuSide = 'top' | 'right' | 'bottom' | 'left';\nexport type MenuAlign = 'start' | 'center' | 'end';\n\nexport interface MenuItemData {\n label: string;\n onClick: () => void;\n isSelected?: boolean;\n isDestructive?: boolean;\n isInactive?: boolean;\n intent?: 'negative';\n keepOpenOnClick?: boolean;\n holdDuration?: number;\n icon?: IconComponent;\n subtext?: string;\n showToggle?: boolean;\n toggleValue?: boolean;\n selectionStyle?: MenuItemSelectionStyle;\n}\n\nexport interface MenuSection {\n header?: string;\n items: MenuItemData[];\n}\n\nexport interface MenuProps {\n isOpen: boolean;\n onClose: () => void;\n anchorRef: React.RefObject<HTMLElement | null>;\n items?: MenuItemData[];\n sections?: MenuSection[];\n side?: MenuSide;\n align?: MenuAlign;\n sideOffset?: number;\n alignOffset?: number;\n sideOffsetToken?: string;\n alignOffsetToken?: string;\n width?: string;\n minWidth?: string;\n maxWidth?: string;\n matchAnchorWidth?: boolean;\n matchAnchorWidthOffset?: number;\n usePortal?: boolean;\n}\n\nexport const Menu: React.FC<MenuProps> = ({\n isOpen,\n onClose,\n anchorRef,\n items,\n sections,\n side = 'bottom',\n align = 'start',\n sideOffset = 4,\n alignOffset = 0,\n sideOffsetToken,\n alignOffsetToken,\n width,\n minWidth,\n maxWidth,\n matchAnchorWidth = false,\n matchAnchorWidthOffset = 0,\n usePortal = true,\n}) => {\n const [isClosing, setIsClosing] = useState(false);\n const [shouldRender, setShouldRender] = useState(isOpen);\n const [isPositionReady, setIsPositionReady] = useState(false);\n const [position, setPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 });\n const [menuSize, setMenuSize] = useState<{ width: number; height: number }>({ width: 0, height: 0 });\n const [anchorSize, setAnchorSize] = useState<{ width: number; height: number }>({ width: 0, height: 0 });\n const positionCalculatedRef = useRef(false);\n const menuRef = useRef<HTMLDivElement>(null);\n const lastSectionsRef = useRef<MenuSection[]>([]);\n\n const menuSections: MenuSection[] = sections || (items ? [{ items }] : []);\n\n const hasItems = menuSections.some((s) => s.items.length > 0);\n if (!isClosing && menuSections.length > 0 && hasItems) {\n lastSectionsRef.current = menuSections;\n }\n const sectionsToRender = isClosing ? lastSectionsRef.current : menuSections;\n\n useEffect(() => {\n if (isOpen) {\n setShouldRender(true);\n setIsClosing(false);\n setIsPositionReady(false);\n positionCalculatedRef.current = false;\n } else if (shouldRender) {\n setIsClosing(true);\n const timer = setTimeout(() => {\n setShouldRender(false);\n setIsClosing(false);\n positionCalculatedRef.current = false;\n }, CLOSE_ANIMATION_MS);\n return () => clearTimeout(timer);\n }\n }, [isOpen, shouldRender]);\n\n useLayoutEffect(() => {\n if (!isOpen || !shouldRender || !menuRef.current) return;\n const updateSize = () => {\n if (!menuRef.current) return;\n const rect = menuRef.current.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) setMenuSize({ width: rect.width, height: rect.height });\n };\n updateSize();\n requestAnimationFrame(() => requestAnimationFrame(updateSize));\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(updateSize);\n observer.observe(menuRef.current);\n return () => observer.disconnect();\n }\n }, [isOpen, shouldRender, menuSections.length]);\n\n useLayoutEffect(() => {\n if (!isOpen || !shouldRender || !anchorRef.current || !menuRef.current) return;\n const calculatePosition = () => {\n const anchor = anchorRef.current;\n const menu = menuRef.current;\n if (!anchor || !menu) return;\n const anchorRect = anchor.getBoundingClientRect();\n const menuRect = menu.getBoundingClientRect();\n const menuWidth = menuRect.width || menuSize.width || 200;\n const menuHeight = menuRect.height || menuSize.height || 160;\n setAnchorSize({ width: anchorRect.width, height: anchorRect.height });\n const gap = sideOffsetToken != null ? getCssSpacePx(sideOffsetToken, 12) : sideOffset;\n const alignOff = alignOffsetToken != null ? getCssSpacePx(alignOffsetToken, 4) : alignOffset;\n let x = 0, y = 0;\n switch (side) {\n case 'top':\n y = anchorRect.top - menuHeight - gap;\n x = align === 'start' ? anchorRect.left + alignOff : align === 'end' ? anchorRect.right - menuWidth + alignOff : anchorRect.left + anchorRect.width / 2 - menuWidth / 2 + alignOff;\n break;\n case 'bottom':\n y = anchorRect.bottom + gap;\n x = align === 'start' ? anchorRect.left + alignOff : align === 'end' ? anchorRect.right - menuWidth + alignOff : anchorRect.left + anchorRect.width / 2 - menuWidth / 2 + alignOff;\n break;\n case 'left':\n x = anchorRect.left - menuWidth - gap;\n y = align === 'start' ? anchorRect.top + alignOff : align === 'end' ? anchorRect.bottom - menuHeight + alignOff : anchorRect.top + anchorRect.height / 2 - menuHeight / 2 + alignOff;\n break;\n case 'right':\n x = anchorRect.right + gap;\n y = align === 'start' ? anchorRect.top + alignOff : align === 'end' ? anchorRect.bottom - menuHeight + alignOff : anchorRect.top + anchorRect.height / 2 - menuHeight / 2 + alignOff;\n break;\n }\n const vp = 4;\n setPosition({\n x: Math.min(Math.max(x, vp), window.innerWidth - menuWidth - vp),\n y: Math.min(Math.max(y, vp), window.innerHeight - menuHeight - vp),\n });\n positionCalculatedRef.current = true;\n setIsPositionReady(true);\n };\n if (!positionCalculatedRef.current) {\n calculatePosition();\n requestAnimationFrame(() => { if (!positionCalculatedRef.current) calculatePosition(); });\n }\n const handleScrollResize = () => calculatePosition();\n window.addEventListener('scroll', handleScrollResize, true);\n window.addEventListener('resize', handleScrollResize);\n return () => {\n window.removeEventListener('scroll', handleScrollResize, true);\n window.removeEventListener('resize', handleScrollResize);\n };\n }, [isOpen, shouldRender, anchorRef, side, align, sideOffset, alignOffset, sideOffsetToken, alignOffsetToken, menuSize.width, menuSize.height, menuSections.length, matchAnchorWidthOffset]);\n\n useEffect(() => {\n if (!isOpen || !shouldRender) return;\n const handleClickOutside = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (menuRef.current?.contains(target)) return;\n if (anchorRef.current?.contains(target)) return;\n onClose();\n };\n document.addEventListener('mousedown', handleClickOutside, true);\n return () => document.removeEventListener('mousedown', handleClickOutside, true);\n }, [isOpen, shouldRender, onClose, anchorRef]);\n\n useEffect(() => {\n if (!isOpen) return;\n const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [isOpen, onClose]);\n\n if (!shouldRender || sectionsToRender.length === 0) return null;\n\n const renderMenuItem = (item: MenuItemData, index: number) => {\n if (item.isDestructive) {\n return (\n <DestructiveMenuItem\n key={index}\n label={item.label}\n subtext={item.subtext}\n holdDuration={item.holdDuration ?? 4000}\n onClick={() => { item.onClick(); if (!item.keepOpenOnClick) onClose(); }}\n />\n );\n }\n return (\n <MenuItemComponent\n key={index}\n icon={item.icon}\n label={item.label}\n subtext={item.subtext}\n onClick={() => { if (!item.isInactive) { item.onClick(); if (!item.keepOpenOnClick) onClose(); } }}\n isSelected={item.isSelected}\n isInactive={item.isInactive}\n intent={item.intent}\n showToggle={item.showToggle}\n toggleValue={item.toggleValue}\n selectionStyle={item.selectionStyle}\n />\n );\n };\n\n const menuStyle: React.CSSProperties = {\n position: 'absolute',\n left: 0,\n top: 0,\n transform: `translate(${Math.round(position.x)}px, ${Math.round(position.y)}px)`,\n willChange: 'transform',\n zIndex: 9998,\n visibility: isPositionReady ? 'visible' : 'hidden',\n ...(matchAnchorWidth && anchorSize.width > 0 ? { width: `${anchorSize.width + matchAnchorWidthOffset}px` } : width ? { width } : {}),\n ...(minWidth ? { minWidth } : {}),\n ...(maxWidth ? { maxWidth } : {}),\n };\n\n const menuContent = (\n <div ref={menuRef} className={`${styles.menuContainer} ${isClosing ? styles.closing : ''}`} style={menuStyle} role=\"menu\" aria-label=\"Menu\">\n {sectionsToRender.map((section, sectionIndex) => (\n <Surface\n key={sectionIndex}\n as=\"div\"\n className={styles.menuSection}\n edge={sectionIndex < sectionsToRender.length - 1 ? 'bottom' : undefined}\n elevation=\"none\"\n >\n {section.header && (\n <div className={styles.sectionHeader}>\n <Text variant=\"text-body-small-emphasis\" as=\"span\" className={styles.sectionLabel}>{section.header}</Text>\n </div>\n )}\n {section.items.map((item, itemIndex) => renderMenuItem(item, itemIndex))}\n </Surface>\n ))}\n </div>\n );\n\n if (usePortal) return createPortal(menuContent, document.body);\n return menuContent;\n};\n\nMenu.displayName = 'Menu';\n",
102
- "type": "registry:ui"
103
- },
104
- {
105
- "path": "src/components/Menu/Menu.module.css",
106
- "content": ".menuContainer {\n box-sizing: border-box;\n background-color: var(--color-background-primary);\n border-radius: var(--dimension-radius-125);\n box-shadow: var(--effect-elevation-elevated-floating);\n display: flex;\n flex-direction: column;\n min-width: 200px;\n max-width: 600px;\n overflow: hidden;\n animation: menuFadeIn var(--effect-motion-short-2) forwards;\n backface-visibility: hidden;\n -webkit-font-smoothing: subpixel-antialiased;\n}\n\n@keyframes menuFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.menuContainer.closing {\n animation: menuFadeOut var(--effect-motion-short-3) forwards;\n}\n\n@keyframes menuFadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n.menuSection {\n padding: var(--dimension-space-050);\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n}\n\n.sectionHeader {\n padding: var(--dimension-space-050) var(--dimension-space-075);\n display: flex;\n align-items: center;\n height: var(--dimension-size-400);\n -webkit-user-select: none;\n user-select: none;\n}\n\n.sectionLabel {\n color: var(--color-foreground-secondary);\n text-align: left;\n}\n",
107
- "type": "registry:ui"
108
- },
109
- {
110
- "path": "src/components/Menu/MenuItem.tsx",
111
- "content": "import React, { forwardRef } from 'react';\nimport { Text } from '@/components/Text';\nimport { Surface } from '@/components/Surface';\nimport type { IconComponent } from '@/types';\nimport styles from './MenuItem.module.css';\n\nexport type MenuItemSelectionStyle = 'highlight' | 'radio' | 'noOverlay';\n\nexport interface MenuItemProps {\n label: string;\n onClick: () => void;\n isSelected?: boolean;\n isInactive?: boolean;\n icon?: IconComponent;\n labelColor?: 'primary' | 'secondary' | 'tertiary';\n subtext?: string;\n showToggle?: boolean;\n toggleValue?: boolean;\n intent?: 'negative';\n selectionStyle?: MenuItemSelectionStyle;\n showTrailingChevron?: boolean;\n checkIcon?: IconComponent;\n chevronIcon?: IconComponent;\n}\n\nexport const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(\n ({\n label,\n onClick,\n isSelected = false,\n isInactive = false,\n icon: Icon,\n labelColor,\n subtext,\n showToggle = false,\n toggleValue = false,\n intent,\n selectionStyle = 'highlight',\n showTrailingChevron = false,\n checkIcon: CheckIcon,\n chevronIcon: ChevronIcon,\n }, ref) => {\n const useRadioStyle = selectionStyle === 'radio';\n const noOverlay = selectionStyle === 'noOverlay' || useRadioStyle;\n const showSelectedOverlay = isSelected && !noOverlay;\n\n return (\n <Surface\n ref={ref as React.Ref<HTMLElement>}\n as=\"button\"\n interactive={!isInactive}\n selected={showSelectedOverlay}\n radius=\"sm\"\n className={`${styles.menuItem} ${isSelected ? styles.menuItemSelected : ''} ${isInactive ? styles.menuItemInactive : ''} ${intent === 'negative' ? styles.menuItemNegative : ''} ${labelColor === 'tertiary' ? styles.menuItemLabelTertiary : ''}`}\n onClick={onClick}\n type=\"button\"\n inactive={isInactive}\n >\n {Icon && (\n <div className={styles.iconPrefix}>\n <Icon size={20} />\n </div>\n )}\n <div className={styles.content}>\n <Text\n variant={isSelected ? 'text-body-medium-emphasis' : 'text-body-medium'}\n color={labelColor}\n as=\"span\"\n className={styles.label}\n >\n {label}\n </Text>\n {subtext && (\n <Text variant=\"text-body-small\" as=\"span\" className={styles.subtext}>\n {subtext}\n </Text>\n )}\n </div>\n {showToggle && (\n <div className={styles.toggleSuffix}>\n <div className={`${styles.toggle} ${toggleValue ? styles.toggleOn : ''}`}>\n <div className={styles.toggleThumb} />\n </div>\n </div>\n )}\n {useRadioStyle && isSelected && CheckIcon && (\n <div className={styles.checkSuffix}>\n <CheckIcon size={20} />\n </div>\n )}\n {showTrailingChevron && ChevronIcon && (\n <div className={styles.chevronSuffix}>\n <ChevronIcon size={20} />\n </div>\n )}\n </Surface>\n );\n }\n);\n\nMenuItem.displayName = 'MenuItem';\n",
112
- "type": "registry:ui"
113
- },
114
- {
115
- "path": "src/components/Menu/MenuItem.module.css",
116
- "content": ".menuItem {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-050);\n padding: var(--dimension-space-075);\n border: none;\n cursor: pointer;\n -webkit-user-select: none;\n user-select: none;\n text-align: left;\n width: 100%;\n min-height: var(--dimension-size-400);\n}\n\n.menuItem:has(.subtext) { align-items: flex-start; }\n\n.iconPrefix {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--color-foreground-secondary);\n}\n\n.menuItemSelected .iconPrefix { color: var(--color-foreground-primary); }\n\n.content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-025);\n min-width: 0;\n}\n\n.label { color: var(--color-foreground-secondary); }\n.menuItem:has(.subtext) .label { color: var(--color-foreground-primary); }\n.menuItemSelected .label { color: var(--color-foreground-primary) !important; }\n.menuItemLabelTertiary .label, .menuItemLabelTertiary .iconPrefix { color: var(--color-foreground-tertiary); }\n.menuItemNegative .label { color: var(--color-foreground-bold-negative) !important; }\n\n.subtext { color: var(--color-foreground-secondary); line-height: 1.4; }\n\n.menuItemInactive { opacity: 0.5; cursor: default; pointer-events: none; }\n\n.toggleSuffix { flex-shrink: 0; display: flex; align-items: center; }\n\n.toggle {\n width: calc(var(--dimension-size-400) + var(--dimension-size-050));\n height: var(--dimension-size-250);\n background-color: var(--color-background-translucent);\n border-radius: var(--dimension-radius-125);\n position: relative;\n transition: var(--effect-transition-interaction-background-short-2);\n}\n\n.toggleOn { background-color: var(--color-background-medium-brand); }\n\n.toggleThumb {\n width: var(--dimension-size-200);\n height: var(--dimension-size-200);\n background-color: var(--color-foreground-on-bold-background-primary);\n border-radius: 50%;\n position: absolute;\n top: var(--dimension-space-025);\n left: var(--dimension-space-025);\n transition: transform var(--effect-motion-short-2);\n}\n\n.toggleOn .toggleThumb { transform: translateX(var(--dimension-offset-200)); }\n\n.checkSuffix {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--color-foreground-primary);\n}\n\n.chevronSuffix {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--color-foreground-secondary);\n}\n\n.menuItemSelected .chevronSuffix { color: var(--color-foreground-primary); }\n",
117
- "type": "registry:ui"
118
- },
119
- {
120
- "path": "src/components/Menu/DestructiveMenuItem.tsx",
121
- "content": "import React, { forwardRef, useState, useRef, useEffect } from 'react';\nimport { Text } from '@/components/Text';\nimport styles from './DestructiveMenuItem.module.css';\n\nexport interface DestructiveMenuItemProps {\n label: string;\n onClick: () => void;\n holdDuration?: number;\n subtext?: string;\n}\n\nexport const DestructiveMenuItem = forwardRef<HTMLButtonElement, DestructiveMenuItemProps>(\n ({ label, onClick, holdDuration = 4000, subtext }, ref) => {\n const [progress, setProgress] = useState(0);\n const pressTimerRef = useRef<number | null>(null);\n const startTimeRef = useRef<number>(0);\n const animationFrameRef = useRef<number | null>(null);\n\n const resetState = () => {\n setProgress(0);\n if (pressTimerRef.current) { clearTimeout(pressTimerRef.current); pressTimerRef.current = null; }\n if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); animationFrameRef.current = null; }\n };\n\n const handleMouseDown = (e: React.MouseEvent) => {\n e.stopPropagation();\n e.preventDefault();\n if (holdDuration === 0) { onClick(); return; }\n startTimeRef.current = performance.now();\n const animate = () => {\n const elapsed = performance.now() - startTimeRef.current;\n const newProgress = Math.min((elapsed / holdDuration) * 100, 100);\n setProgress(newProgress);\n if (newProgress < 100) animationFrameRef.current = requestAnimationFrame(animate);\n };\n animationFrameRef.current = requestAnimationFrame(animate);\n pressTimerRef.current = window.setTimeout(() => { onClick(); resetState(); }, holdDuration);\n };\n\n const handleMouseUp = (e: React.MouseEvent) => { e.stopPropagation(); resetState(); };\n const handleMouseLeave = (e: React.MouseEvent) => { e.stopPropagation(); resetState(); };\n\n useEffect(() => () => { resetState(); }, []);\n\n return (\n <button\n ref={ref}\n className={styles.destructiveMenuItem}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n type=\"button\"\n >\n <div className={styles.progressFill} style={{ width: `${progress}%` }} />\n <div className={styles.content}>\n <Text variant=\"text-body-medium\" as=\"span\" className={styles.label}>{label}</Text>\n {subtext && <Text variant=\"text-body-small\" as=\"span\" className={styles.subtext}>{subtext}</Text>}\n </div>\n </button>\n );\n }\n);\n\nDestructiveMenuItem.displayName = 'DestructiveMenuItem';\n",
122
- "type": "registry:ui"
123
- },
124
- {
125
- "path": "src/components/Menu/DestructiveMenuItem.module.css",
126
- "content": ".destructiveMenuItem {\n display: flex;\n align-items: center;\n padding: var(--dimension-space-075);\n background-color: transparent;\n border: none;\n border-radius: var(--dimension-radius-075);\n cursor: pointer;\n -webkit-user-select: none;\n user-select: none;\n text-align: left;\n width: 100%;\n min-height: var(--dimension-size-400);\n position: relative;\n transition: var(--effect-transition-interaction-background-instant);\n overflow: hidden;\n}\n\n.destructiveMenuItem:has(.subtext) { align-items: flex-start; }\n\n.progressFill {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n background-color: var(--color-background-faint-negative);\n border-radius: var(--dimension-radius-075);\n transition: width 0ms linear;\n pointer-events: none;\n z-index: 0;\n}\n\n.destructiveMenuItem::before {\n content: '';\n position: absolute;\n inset: 0;\n background-color: transparent;\n border-radius: var(--dimension-radius-075);\n transition: var(--effect-transition-interaction-background-instant);\n pointer-events: none;\n z-index: 1;\n}\n\n.content {\n position: relative;\n z-index: 2;\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-025);\n min-width: 0;\n}\n\n.label { color: var(--color-foreground-bold-negative); }\n.subtext { color: var(--color-foreground-secondary); text-wrap: pretty; }\n\n.destructiveMenuItem:hover::before { background-color: var(--color-interaction-hover); }\n.destructiveMenuItem:active::before { background-color: transparent; }\n",
127
- "type": "registry:ui"
128
- }
129
- ]
130
- }
package/dist/r/modal.json DELETED
@@ -1,93 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "modal",
4
- "title": "Modal",
5
- "description": "Dialog overlay with portal rendering, escape-to-close, backdrop click, focus management, and entry/exit animations.",
6
- "type": "registry:ui",
7
- "meta": {
8
- "library": "@compomo/ui",
9
- "distribution": "npm",
10
- "storybook": "https://zainadeel.github.io/CompoMo/?path=/story/modal",
11
- "consumption": {
12
- "install": "npm install @compomo/ui @ds-mo/tokens",
13
- "cssSetup": "Import tokens CSS at your app entry point: import '@ds-mo/tokens/css';\nThis provides all var(--color-*), var(--dimension-*), var(--effect-*), and var(--typography-*) custom properties.\nFor dark mode, set data-theme=\"dark\" on <html>.",
14
- "import": "import { Modal } from '@compomo/ui';",
15
- "cssImport": "import '@compomo/ui/css';",
16
- "typeImport": "import type { ModalProps, ModalWidth } from '@compomo/ui';",
17
- "internalDependencies": [
18
- {
19
- "component": "Surface",
20
- "note": "Used internally by Modal. No action needed — it's bundled in @compomo/ui."
21
- },
22
- {
23
- "component": "Text",
24
- "note": "Used internally by Modal. No action needed — it's bundled in @compomo/ui."
25
- }
26
- ],
27
- "peerDependencies": {
28
- "required": [
29
- "react >=17",
30
- "react-dom >=17",
31
- "@ds-mo/tokens >=0.1.0"
32
- ],
33
- "optional": []
34
- }
35
- },
36
- "props": {
37
- "isOpen": {
38
- "type": "boolean",
39
- "required": true
40
- },
41
- "onClose": {
42
- "type": "() => void",
43
- "required": true
44
- },
45
- "title": {
46
- "type": "string",
47
- "required": true
48
- },
49
- "children": {
50
- "type": "React.ReactNode",
51
- "required": true
52
- },
53
- "subtitle": {
54
- "type": "React.ReactNode"
55
- },
56
- "footer": {
57
- "type": "React.ReactNode"
58
- },
59
- "width": {
60
- "type": "'sm' | 'md' | 'lg' | string",
61
- "default": "'md'",
62
- "description": "sm=400px, md=560px, lg=720px, or custom CSS value."
63
- }
64
- },
65
- "exports": [
66
- "Modal"
67
- ],
68
- "types": [
69
- "ModalProps",
70
- "ModalWidth"
71
- ]
72
- },
73
- "dependencies": [
74
- "@compomo/ui",
75
- "@ds-mo/tokens"
76
- ],
77
- "registryDependencies": [
78
- "surface",
79
- "text"
80
- ],
81
- "files": [
82
- {
83
- "path": "src/components/Modal/Modal.module.css",
84
- "content": ".backdrop {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--dimension-space-200);\n background-color: var(--color-background-shade);\n backdrop-filter: blur(var(--effect-blur-sm));\n -webkit-backdrop-filter: blur(var(--effect-blur-sm));\n animation: modalBackdropFadeIn var(--effect-motion-short-2) forwards;\n z-index: 9999;\n}\n\n.backdrop.closing {\n animation: modalBackdropFadeOut var(--effect-motion-short-3) forwards;\n}\n\n@keyframes modalBackdropFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes modalBackdropFadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n.dialog {\n display: flex;\n flex-direction: column;\n max-height: calc(100vh - 32px);\n overflow: hidden;\n animation: modalDialogFadeIn var(--effect-motion-short-2) forwards;\n}\n\n.backdrop.closing .dialog {\n animation: modalDialogFadeOut var(--effect-motion-short-3) forwards;\n}\n\n@keyframes modalDialogFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes modalDialogFadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n.header {\n padding: var(--dimension-space-200) var(--dimension-space-200) var(--dimension-space-100) var(--dimension-space-200);\n}\n\n.subtitle {\n margin-top: var(--dimension-space-100);\n text-wrap: balance;\n}\n\n.body {\n padding: var(--dimension-space-100) var(--dimension-space-200);\n overflow-y: auto;\n}\n\n.footer {\n padding: var(--dimension-space-100) var(--dimension-space-200) var(--dimension-space-200) var(--dimension-space-200);\n}\n",
85
- "type": "registry:ui"
86
- },
87
- {
88
- "path": "src/components/Modal/Modal.tsx",
89
- "content": "import React, { useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Surface } from '@/components/Surface';\nimport { Text } from '@/components/Text';\nimport styles from './Modal.module.css';\n\nexport type ModalWidth = 'sm' | 'md' | 'lg';\n\nconst WIDTH_MAP: Record<ModalWidth, string> = {\n sm: 'var(--dimension-modal-width-sm, 400px)',\n md: 'var(--dimension-modal-width-md, 560px)',\n lg: 'var(--dimension-modal-width-lg, 720px)',\n};\n\nconst CLOSE_ANIMATION_MS = 220;\n\nexport interface ModalProps {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n subtitle?: React.ReactNode;\n children: React.ReactNode;\n footer?: React.ReactNode;\n width?: ModalWidth | string;\n bodyClassName?: string;\n}\n\nexport const Modal: React.FC<ModalProps> = ({\n isOpen,\n onClose,\n title,\n subtitle,\n children,\n footer,\n width = 'md',\n bodyClassName,\n}) => {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const [isClosing, setIsClosing] = useState(false);\n const [shouldRender, setShouldRender] = useState(isOpen);\n\n const resolvedWidth = useMemo(() => {\n if (width === 'sm' || width === 'md' || width === 'lg') {\n return WIDTH_MAP[width];\n }\n return width;\n }, [width]);\n\n useEffect(() => {\n if (isOpen) {\n setShouldRender(true);\n setIsClosing(false);\n } else if (shouldRender) {\n setIsClosing(true);\n const timer = setTimeout(() => {\n setShouldRender(false);\n setIsClosing(false);\n }, CLOSE_ANIMATION_MS);\n return () => clearTimeout(timer);\n }\n }, [isOpen, shouldRender]);\n\n useEffect(() => {\n if (!isOpen) return;\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n };\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [isOpen, onClose]);\n\n useEffect(() => {\n if (!isOpen || !shouldRender) return;\n const id = requestAnimationFrame(() => {\n dialogRef.current?.focus();\n });\n return () => cancelAnimationFrame(id);\n }, [isOpen, shouldRender]);\n\n if (!shouldRender) return null;\n\n return createPortal(\n <div\n className={`${styles.backdrop} ${isClosing ? styles.closing : ''}`}\n onMouseDown={(e) => {\n if (e.target === e.currentTarget) onClose();\n }}\n >\n <Surface\n ref={dialogRef}\n background=\"primary\"\n elevation=\"floating\"\n radius=\"var(--dimension-radius-275, 22px)\"\n className={styles.dialog}\n style={{ width: `min(${resolvedWidth}, calc(100vw - 32px))` }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n tabIndex={-1}\n >\n <div className={styles.header}>\n <Text id={titleId} as=\"h2\" variant=\"text-title-small\">\n {title}\n </Text>\n {subtitle != null && (\n <div className={styles.subtitle}>{subtitle}</div>\n )}\n </div>\n <div className={`${styles.body} ${bodyClassName ?? ''}`.trim()}>\n {children}\n </div>\n {footer != null && (\n <div className={styles.footer}>{footer}</div>\n )}\n </Surface>\n </div>,\n document.body\n );\n};\n\nModal.displayName = 'Modal';\n",
90
- "type": "registry:ui"
91
- }
92
- ]
93
- }
@@ -1,80 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "pagination",
4
- "title": "Pagination",
5
- "description": "Standalone page navigation with ellipsis for large ranges, sibling count control, and keyboard-accessible buttons.",
6
- "type": "registry:ui",
7
- "meta": {
8
- "library": "@compomo/ui",
9
- "distribution": "npm",
10
- "storybook": "https://zainadeel.github.io/CompoMo/?path=/story/pagination",
11
- "consumption": {
12
- "install": "npm install @compomo/ui @ds-mo/tokens",
13
- "cssSetup": "Import tokens CSS at your app entry point: import '@ds-mo/tokens/css';\nThis provides all var(--color-*), var(--dimension-*), var(--effect-*), and var(--typography-*) custom properties.\nFor dark mode, set data-theme=\"dark\" on <html>.",
14
- "import": "import { Pagination } from '@compomo/ui';",
15
- "cssImport": "import '@compomo/ui/css';",
16
- "typeImport": "import type { PaginationProps } from '@compomo/ui';",
17
- "internalDependencies": [
18
- {
19
- "component": "Text",
20
- "note": "Used internally by Pagination. No action needed — it's bundled in @compomo/ui."
21
- }
22
- ],
23
- "peerDependencies": {
24
- "required": [
25
- "react >=17",
26
- "react-dom >=17",
27
- "@ds-mo/tokens >=0.1.0"
28
- ],
29
- "optional": []
30
- }
31
- },
32
- "props": {
33
- "page": {
34
- "type": "number",
35
- "required": true,
36
- "description": "1-based current page."
37
- },
38
- "totalPages": {
39
- "type": "number",
40
- "required": true
41
- },
42
- "onPageChange": {
43
- "type": "(page: number) => void",
44
- "required": true
45
- },
46
- "siblingCount": {
47
- "type": "number",
48
- "default": "1"
49
- },
50
- "inactive": {
51
- "type": "boolean"
52
- }
53
- },
54
- "exports": [
55
- "Pagination"
56
- ],
57
- "types": [
58
- "PaginationProps"
59
- ]
60
- },
61
- "dependencies": [
62
- "@compomo/ui",
63
- "@ds-mo/tokens"
64
- ],
65
- "registryDependencies": [
66
- "text"
67
- ],
68
- "files": [
69
- {
70
- "path": "src/components/Pagination/Pagination.module.css",
71
- "content": ".pagination {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-025);\n -webkit-user-select: none;\n user-select: none;\n}\n\n.inactive {\n opacity: 0.5;\n pointer-events: none;\n}\n\n/* ─── Button ────────────────────────────────────────────────────────────────── */\n\n.button {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n padding: 0 var(--dimension-space-050);\n background: none;\n border: none;\n border-radius: var(--dimension-radius-075);\n cursor: pointer;\n color: var(--color-foreground-secondary);\n transition: background-color var(--effect-motion-short-2), color var(--effect-motion-short-2);\n}\n\n.button:hover:not(:disabled) {\n background-color: var(--color-interaction-hover);\n color: var(--color-foreground-primary);\n}\n\n.button:active:not(:disabled) {\n background-color: var(--color-interaction-pressed);\n}\n\n.button:disabled {\n cursor: not-allowed;\n opacity: 0.35;\n}\n\n.active {\n background-color: var(--color-interaction-hover);\n color: var(--color-foreground-primary);\n}\n\n/* ─── Nav arrows ────────────────────────────────────────────────────────────── */\n\n.nav {\n font-size: 18px;\n}\n\n/* ─── Ellipsis ──────────────────────────────────────────────────────────────── */\n\n.ellipsis {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n}\n",
72
- "type": "registry:ui"
73
- },
74
- {
75
- "path": "src/components/Pagination/Pagination.tsx",
76
- "content": "import React, { forwardRef, useMemo } from 'react';\nimport { cn } from '@/utils/cn';\nimport { Text } from '@/components/Text';\nimport styles from './Pagination.module.css';\n\nexport interface PaginationProps {\n /** Current page (1-based). */\n page: number;\n /** Total number of pages. */\n totalPages: number;\n /** Called when page changes. */\n onPageChange: (page: number) => void;\n /** Max page buttons visible (excluding prev/next). */\n siblingCount?: number;\n /** Disables all interaction. */\n inactive?: boolean;\n className?: string;\n}\n\nfunction buildRange(start: number, end: number): (number | 'ellipsis')[] {\n const items: (number | 'ellipsis')[] = [];\n for (let i = start; i <= end; i++) items.push(i);\n return items;\n}\n\nfunction buildPages(page: number, totalPages: number, siblings: number): (number | 'ellipsis')[] {\n const totalSlots = siblings * 2 + 5; // siblings + boundaries + ellipses + current\n\n if (totalPages <= totalSlots) return buildRange(1, totalPages);\n\n const leftSibling = Math.max(page - siblings, 2);\n const rightSibling = Math.min(page + siblings, totalPages - 1);\n\n const showLeftEllipsis = leftSibling > 2;\n const showRightEllipsis = rightSibling < totalPages - 1;\n\n if (!showLeftEllipsis && showRightEllipsis) {\n const left = buildRange(1, siblings * 2 + 3);\n return [...left, 'ellipsis', totalPages];\n }\n\n if (showLeftEllipsis && !showRightEllipsis) {\n const right = buildRange(totalPages - (siblings * 2 + 2), totalPages);\n return [1, 'ellipsis', ...right];\n }\n\n return [1, 'ellipsis', ...buildRange(leftSibling, rightSibling), 'ellipsis', totalPages];\n}\n\nexport const Pagination = forwardRef<HTMLElement, PaginationProps>(\n ({ page, totalPages, onPageChange, siblingCount = 1, inactive = false, className }, ref) => {\n const pages = useMemo(() => buildPages(page, totalPages, siblingCount), [page, totalPages, siblingCount]);\n\n if (totalPages <= 1) return null;\n\n return (\n <nav ref={ref} aria-label=\"Pagination\" className={cn(styles.pagination, inactive && styles.inactive, className)}>\n <button\n type=\"button\"\n className={cn(styles.button, styles.nav)}\n disabled={page <= 1 || inactive}\n onClick={() => onPageChange(page - 1)}\n aria-label=\"Previous page\"\n >\n <Text variant=\"text-body-small\" as=\"span\" color=\"inherit\">&#x2039;</Text>\n </button>\n\n {pages.map((item, i) =>\n item === 'ellipsis' ? (\n <span key={`e${i}`} className={styles.ellipsis}>\n <Text variant=\"text-body-small\" as=\"span\" color=\"tertiary\">&hellip;</Text>\n </span>\n ) : (\n <button\n key={item}\n type=\"button\"\n className={cn(styles.button, item === page && styles.active)}\n onClick={() => onPageChange(item)}\n aria-current={item === page ? 'page' : undefined}\n disabled={inactive}\n >\n <Text variant={item === page ? 'text-body-small-emphasis' : 'text-body-small'} as=\"span\" color=\"inherit\">\n {item}\n </Text>\n </button>\n )\n )}\n\n <button\n type=\"button\"\n className={cn(styles.button, styles.nav)}\n disabled={page >= totalPages || inactive}\n onClick={() => onPageChange(page + 1)}\n aria-label=\"Next page\"\n >\n <Text variant=\"text-body-small\" as=\"span\" color=\"inherit\">&#x203A;</Text>\n </button>\n </nav>\n );\n }\n);\n\nPagination.displayName = 'Pagination';\n",
77
- "type": "registry:ui"
78
- }
79
- ]
80
- }
package/dist/r/radio.json DELETED
@@ -1,85 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "radio",
4
- "title": "RadioGroup",
5
- "description": "Radio selection group with vertical/horizontal layout, per-option inactive, and keyboard navigation.",
6
- "type": "registry:ui",
7
- "meta": {
8
- "library": "@compomo/ui",
9
- "distribution": "npm",
10
- "storybook": "https://zainadeel.github.io/CompoMo/?path=/story/radio",
11
- "consumption": {
12
- "install": "npm install @compomo/ui @ds-mo/tokens",
13
- "cssSetup": "Import tokens CSS at your app entry point: import '@ds-mo/tokens/css';\nThis provides all var(--color-*), var(--dimension-*), var(--effect-*), and var(--typography-*) custom properties.\nFor dark mode, set data-theme=\"dark\" on <html>.",
14
- "import": "import { RadioGroup, RadioItem } from '@compomo/ui';",
15
- "cssImport": "import '@compomo/ui/css';",
16
- "typeImport": "import type { RadioGroupProps, RadioItemProps, RadioOption } from '@compomo/ui';",
17
- "internalDependencies": [
18
- {
19
- "component": "Text",
20
- "note": "Used internally by RadioGroup. No action needed — it's bundled in @compomo/ui."
21
- }
22
- ],
23
- "peerDependencies": {
24
- "required": [
25
- "react >=17",
26
- "react-dom >=17",
27
- "@ds-mo/tokens >=0.1.0"
28
- ],
29
- "optional": []
30
- }
31
- },
32
- "props": {
33
- "value": {
34
- "type": "string",
35
- "required": true
36
- },
37
- "onChange": {
38
- "type": "(value: string) => void",
39
- "required": true
40
- },
41
- "options": {
42
- "type": "RadioOption[]",
43
- "required": true
44
- },
45
- "direction": {
46
- "type": "'vertical' | 'horizontal'",
47
- "default": "'vertical'"
48
- },
49
- "inactive": {
50
- "type": "boolean"
51
- },
52
- "aria-label": {
53
- "type": "string"
54
- }
55
- },
56
- "exports": [
57
- "RadioGroup",
58
- "RadioItem"
59
- ],
60
- "types": [
61
- "RadioGroupProps",
62
- "RadioItemProps",
63
- "RadioOption"
64
- ]
65
- },
66
- "dependencies": [
67
- "@compomo/ui",
68
- "@ds-mo/tokens"
69
- ],
70
- "registryDependencies": [
71
- "text"
72
- ],
73
- "files": [
74
- {
75
- "path": "src/components/Radio/Radio.module.css",
76
- "content": ".group {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-025);\n}\n\n.horizontal {\n flex-direction: row;\n gap: var(--dimension-space-100);\n}\n\n/* ─── Radio item ────────────────────────────────────────────────────────────── */\n\n.radio {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-050);\n padding: var(--dimension-space-075);\n height: var(--dimension-size-400);\n box-sizing: border-box;\n cursor: pointer;\n -webkit-user-select: none;\n user-select: none;\n border-radius: var(--dimension-radius-075);\n transition: background-color var(--effect-motion-short-2);\n}\n\n.radio:hover { background-color: var(--color-interaction-hover); }\n.radio:active { background-color: var(--color-interaction-pressed); }\n\n.inactive {\n cursor: not-allowed;\n opacity: 0.5;\n pointer-events: none;\n}\n\n/* ─── Circle indicator ──────────────────────────────────────────────────────── */\n\n.circle {\n width: 20px;\n height: 20px;\n border: 1.5px solid var(--color-foreground-secondary);\n border-radius: var(--dimension-radius-half);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: all var(--effect-motion-short-2);\n box-sizing: border-box;\n}\n\n.circleChecked {\n border-color: var(--color-foreground-primary);\n}\n\n.dot {\n width: 10px;\n height: 10px;\n border-radius: var(--dimension-radius-half);\n background-color: var(--color-foreground-primary);\n}\n",
77
- "type": "registry:ui"
78
- },
79
- {
80
- "path": "src/components/Radio/Radio.tsx",
81
- "content": "import React, { forwardRef } from 'react';\nimport { cn } from '@/utils/cn';\nimport { Text } from '@/components/Text';\nimport styles from './Radio.module.css';\n\nexport interface RadioOption {\n label: string;\n value: string;\n inactive?: boolean;\n}\n\nexport interface RadioGroupProps {\n /** Currently selected value. */\n value: string;\n /** Called when selection changes. */\n onChange: (value: string) => void;\n /** Radio options to render. */\n options: RadioOption[];\n /** Group label for accessibility. */\n 'aria-label'?: string;\n /** Layout direction. */\n direction?: 'vertical' | 'horizontal';\n /** Disables the entire group. */\n inactive?: boolean;\n className?: string;\n}\n\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(\n (\n {\n value,\n onChange,\n options,\n 'aria-label': ariaLabel,\n direction = 'vertical',\n inactive = false,\n className,\n },\n ref\n ) => (\n <div\n ref={ref}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n className={cn(\n styles.group,\n direction === 'horizontal' && styles.horizontal,\n className\n )}\n >\n {options.map(opt => (\n <RadioItem\n key={opt.value}\n label={opt.label}\n checked={value === opt.value}\n inactive={inactive || opt.inactive}\n onChange={() => onChange(opt.value)}\n />\n ))}\n </div>\n )\n);\n\nRadioGroup.displayName = 'RadioGroup';\n\n/* ─── Individual Radio Item ─────────────────────────────────────────────────── */\n\nexport interface RadioItemProps {\n label: string;\n checked?: boolean;\n onChange?: () => void;\n inactive?: boolean;\n className?: string;\n}\n\nexport const RadioItem = forwardRef<HTMLDivElement, RadioItemProps>(\n ({ label, checked = false, onChange, inactive = false, className }, ref) => {\n const handleClick = () => {\n if (!inactive && !checked) onChange?.();\n };\n\n return (\n <div\n ref={ref}\n role=\"radio\"\n aria-checked={checked}\n tabIndex={inactive ? -1 : 0}\n className={cn(styles.radio, inactive && styles.inactive, className)}\n onClick={handleClick}\n onKeyDown={e => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n handleClick();\n }\n }}\n >\n <span className={cn(styles.circle, checked && styles.circleChecked)}>\n {checked && <span className={styles.dot} />}\n </span>\n <Text variant=\"text-body-medium\" as=\"span\" color=\"inherit\">\n {label}\n </Text>\n </div>\n );\n }\n);\n\nRadioItem.displayName = 'RadioItem';\n",
82
- "type": "registry:ui"
83
- }
84
- ]
85
- }