@primereact/mcp 11.0.0-rc.2

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 (546) hide show
  1. package/LICENSE.md +45 -0
  2. package/README.md +94 -0
  3. package/data/llms/headless/add-ons/introduction.md +16 -0
  4. package/data/llms/headless/components/accordion/api.json +252 -0
  5. package/data/llms/headless/components/accordion.md +196 -0
  6. package/data/llms/headless/components/animateonscroll/api.json +88 -0
  7. package/data/llms/headless/components/animateonscroll.md +141 -0
  8. package/data/llms/headless/components/autocomplete/api.json +787 -0
  9. package/data/llms/headless/components/autocomplete.md +249 -0
  10. package/data/llms/headless/components/avatar/api.json +154 -0
  11. package/data/llms/headless/components/avatar.md +149 -0
  12. package/data/llms/headless/components/badge/api.json +68 -0
  13. package/data/llms/headless/components/badge.md +150 -0
  14. package/data/llms/headless/components/breadcrumb/api.json +142 -0
  15. package/data/llms/headless/components/breadcrumb.md +149 -0
  16. package/data/llms/headless/components/button/api.json +62 -0
  17. package/data/llms/headless/components/button.md +73 -0
  18. package/data/llms/headless/components/card/api.json +36 -0
  19. package/data/llms/headless/components/card.md +96 -0
  20. package/data/llms/headless/components/carousel/api.json +793 -0
  21. package/data/llms/headless/components/carousel.md +196 -0
  22. package/data/llms/headless/components/checkbox/api.json +593 -0
  23. package/data/llms/headless/components/checkbox.md +184 -0
  24. package/data/llms/headless/components/chip/api.json +107 -0
  25. package/data/llms/headless/components/chip.md +141 -0
  26. package/data/llms/headless/components/collapsible/api.json +301 -0
  27. package/data/llms/headless/components/collapsible.md +136 -0
  28. package/data/llms/headless/components/compare/api.json +533 -0
  29. package/data/llms/headless/components/compare.md +142 -0
  30. package/data/llms/headless/components/contextmenu/api.json +495 -0
  31. package/data/llms/headless/components/contextmenu.md +282 -0
  32. package/data/llms/headless/components/datatable/api.json +1680 -0
  33. package/data/llms/headless/components/datatable.md +258 -0
  34. package/data/llms/headless/components/dataview/api.json +199 -0
  35. package/data/llms/headless/components/dataview.md +147 -0
  36. package/data/llms/headless/components/datepicker/api.json +2796 -0
  37. package/data/llms/headless/components/datepicker.md +456 -0
  38. package/data/llms/headless/components/dialog/api.json +626 -0
  39. package/data/llms/headless/components/dialog.md +280 -0
  40. package/data/llms/headless/components/divider/api.json +130 -0
  41. package/data/llms/headless/components/divider.md +92 -0
  42. package/data/llms/headless/components/drawer/api.json +425 -0
  43. package/data/llms/headless/components/drawer.md +220 -0
  44. package/data/llms/headless/components/fieldset/api.json +195 -0
  45. package/data/llms/headless/components/fieldset.md +154 -0
  46. package/data/llms/headless/components/fileupload/api.json +905 -0
  47. package/data/llms/headless/components/fileupload.md +323 -0
  48. package/data/llms/headless/components/floatlabel/api.json +36 -0
  49. package/data/llms/headless/components/floatlabel.md +19 -0
  50. package/data/llms/headless/components/fluid/api.json +36 -0
  51. package/data/llms/headless/components/fluid.md +19 -0
  52. package/data/llms/headless/components/focustrap/api.json +135 -0
  53. package/data/llms/headless/components/focustrap.md +125 -0
  54. package/data/llms/headless/components/gallery/api.json +1349 -0
  55. package/data/llms/headless/components/gallery.md +355 -0
  56. package/data/llms/headless/components/iconfield/api.json +62 -0
  57. package/data/llms/headless/components/iconfield.md +19 -0
  58. package/data/llms/headless/components/iftalabel/api.json +62 -0
  59. package/data/llms/headless/components/iftalabel.md +19 -0
  60. package/data/llms/headless/components/inplace/api.json +340 -0
  61. package/data/llms/headless/components/inplace.md +153 -0
  62. package/data/llms/headless/components/inputcolor/api.json +469 -0
  63. package/data/llms/headless/components/inputcolor.md +253 -0
  64. package/data/llms/headless/components/inputgroup/api.json +62 -0
  65. package/data/llms/headless/components/inputgroup.md +19 -0
  66. package/data/llms/headless/components/inputnumber/api.json +554 -0
  67. package/data/llms/headless/components/inputnumber.md +167 -0
  68. package/data/llms/headless/components/inputotp/api.json +276 -0
  69. package/data/llms/headless/components/inputotp.md +130 -0
  70. package/data/llms/headless/components/inputpassword/api.json +180 -0
  71. package/data/llms/headless/components/inputpassword.md +117 -0
  72. package/data/llms/headless/components/inputtags/api.json +497 -0
  73. package/data/llms/headless/components/inputtags.md +298 -0
  74. package/data/llms/headless/components/inputtext/api.json +81 -0
  75. package/data/llms/headless/components/inputtext.md +109 -0
  76. package/data/llms/headless/components/knob/api.json +429 -0
  77. package/data/llms/headless/components/knob.md +151 -0
  78. package/data/llms/headless/components/label/api.json +62 -0
  79. package/data/llms/headless/components/label.md +19 -0
  80. package/data/llms/headless/components/listbox/api.json +814 -0
  81. package/data/llms/headless/components/listbox.md +194 -0
  82. package/data/llms/headless/components/menu/api.json +1247 -0
  83. package/data/llms/headless/components/menu.md +283 -0
  84. package/data/llms/headless/components/message/api.json +131 -0
  85. package/data/llms/headless/components/message.md +143 -0
  86. package/data/llms/headless/components/metergroup/api.json +266 -0
  87. package/data/llms/headless/components/metergroup.md +129 -0
  88. package/data/llms/headless/components/motion/api.json +237 -0
  89. package/data/llms/headless/components/motion.md +160 -0
  90. package/data/llms/headless/components/navigationmenu/api.json +220 -0
  91. package/data/llms/headless/components/navigationmenu.md +166 -0
  92. package/data/llms/headless/components/orderlist/api.json +196 -0
  93. package/data/llms/headless/components/orderlist.md +221 -0
  94. package/data/llms/headless/components/organizationchart/api.json +372 -0
  95. package/data/llms/headless/components/organizationchart.md +254 -0
  96. package/data/llms/headless/components/paginator/api.json +311 -0
  97. package/data/llms/headless/components/paginator.md +187 -0
  98. package/data/llms/headless/components/panel/api.json +195 -0
  99. package/data/llms/headless/components/panel.md +150 -0
  100. package/data/llms/headless/components/picklist/api.json +293 -0
  101. package/data/llms/headless/components/picklist.md +270 -0
  102. package/data/llms/headless/components/popover/api.json +405 -0
  103. package/data/llms/headless/components/popover.md +301 -0
  104. package/data/llms/headless/components/portal/api.json +56 -0
  105. package/data/llms/headless/components/portal.md +102 -0
  106. package/data/llms/headless/components/positioner/api.json +165 -0
  107. package/data/llms/headless/components/positioner.md +181 -0
  108. package/data/llms/headless/components/progressbar/api.json +87 -0
  109. package/data/llms/headless/components/progressbar.md +122 -0
  110. package/data/llms/headless/components/progressspinner/api.json +337 -0
  111. package/data/llms/headless/components/progressspinner.md +128 -0
  112. package/data/llms/headless/components/radiobutton/api.json +569 -0
  113. package/data/llms/headless/components/radiobutton.md +195 -0
  114. package/data/llms/headless/components/rating/api.json +347 -0
  115. package/data/llms/headless/components/rating.md +181 -0
  116. package/data/llms/headless/components/scrollarea/api.json +730 -0
  117. package/data/llms/headless/components/scrollarea.md +232 -0
  118. package/data/llms/headless/components/select/api.json +829 -0
  119. package/data/llms/headless/components/select.md +262 -0
  120. package/data/llms/headless/components/sidebar/api.json +433 -0
  121. package/data/llms/headless/components/sidebar.md +266 -0
  122. package/data/llms/headless/components/skeleton/api.json +68 -0
  123. package/data/llms/headless/components/skeleton.md +95 -0
  124. package/data/llms/headless/components/slider/api.json +559 -0
  125. package/data/llms/headless/components/slider.md +158 -0
  126. package/data/llms/headless/components/speeddial/api.json +459 -0
  127. package/data/llms/headless/components/speeddial.md +181 -0
  128. package/data/llms/headless/components/splitter/api.json +582 -0
  129. package/data/llms/headless/components/splitter.md +159 -0
  130. package/data/llms/headless/components/stepper/api.json +385 -0
  131. package/data/llms/headless/components/stepper.md +183 -0
  132. package/data/llms/headless/components/styleclass/api.json +134 -0
  133. package/data/llms/headless/components/styleclass.md +129 -0
  134. package/data/llms/headless/components/tabs/api.json +336 -0
  135. package/data/llms/headless/components/tabs.md +158 -0
  136. package/data/llms/headless/components/tag/api.json +62 -0
  137. package/data/llms/headless/components/tag.md +121 -0
  138. package/data/llms/headless/components/terminal/api.json +117 -0
  139. package/data/llms/headless/components/terminal.md +183 -0
  140. package/data/llms/headless/components/textarea/api.json +81 -0
  141. package/data/llms/headless/components/textarea.md +110 -0
  142. package/data/llms/headless/components/timeline/api.json +270 -0
  143. package/data/llms/headless/components/timeline.md +117 -0
  144. package/data/llms/headless/components/toast/api.json +455 -0
  145. package/data/llms/headless/components/toast.md +277 -0
  146. package/data/llms/headless/components/togglebutton/api.json +357 -0
  147. package/data/llms/headless/components/togglebutton.md +183 -0
  148. package/data/llms/headless/components/toggleswitch/api.json +326 -0
  149. package/data/llms/headless/components/toggleswitch.md +164 -0
  150. package/data/llms/headless/components/toolbar/api.json +68 -0
  151. package/data/llms/headless/components/toolbar.md +96 -0
  152. package/data/llms/headless/components/tooltip/api.json +478 -0
  153. package/data/llms/headless/components/tooltip.md +233 -0
  154. package/data/llms/headless/components/tree/api.json +1297 -0
  155. package/data/llms/headless/components/tree.md +297 -0
  156. package/data/llms/headless/components/treetable/api.json +1680 -0
  157. package/data/llms/headless/components/treetable.md +177 -0
  158. package/data/llms/headless/guides/ai/llms.md +25 -0
  159. package/data/llms/headless/guides/installation.md +126 -0
  160. package/data/llms/headless/guides/introduction.md +20 -0
  161. package/data/llms/headless/guides/misc/accessibility.md +228 -0
  162. package/data/llms/headless/llms.md +25 -0
  163. package/data/llms/headless/resources/introduction.md +15 -0
  164. package/data/llms/hooks/gettingstarted/introduction.md +34 -0
  165. package/data/llms/hooks/gettingstarted/setup.md +54 -0
  166. package/data/llms/hooks/use-attr-selector.md +25 -0
  167. package/data/llms/hooks/use-controlled-state.md +31 -0
  168. package/data/llms/hooks/use-event-listener.md +37 -0
  169. package/data/llms/hooks/use-filter.md +312 -0
  170. package/data/llms/hooks/use-hot-key.md +40 -0
  171. package/data/llms/hooks/use-id.md +24 -0
  172. package/data/llms/hooks/use-intersection-observer.md +42 -0
  173. package/data/llms/hooks/use-is-mobile.md +24 -0
  174. package/data/llms/hooks/use-key-filter.md +139 -0
  175. package/data/llms/hooks/use-local-storage.md +28 -0
  176. package/data/llms/hooks/use-mask.md +53 -0
  177. package/data/llms/hooks/use-match-media.md +28 -0
  178. package/data/llms/hooks/use-mount-effect.md +25 -0
  179. package/data/llms/hooks/use-mounted.md +36 -0
  180. package/data/llms/hooks/use-number-formatter.md +48 -0
  181. package/data/llms/hooks/use-presence.md +44 -0
  182. package/data/llms/hooks/use-previous.md +28 -0
  183. package/data/llms/hooks/use-props.md +37 -0
  184. package/data/llms/hooks/use-queue-task.md +29 -0
  185. package/data/llms/hooks/use-scrolltop.md +87 -0
  186. package/data/llms/hooks/use-sortable-list.md +226 -0
  187. package/data/llms/hooks/use-tree-filter.md +216 -0
  188. package/data/llms/hooks/use-unmount-effect.md +23 -0
  189. package/data/llms/hooks/use-update-effect.md +27 -0
  190. package/data/llms/hooks/use-view-transition.md +31 -0
  191. package/data/llms/hooks/use-visibility-change.md +30 -0
  192. package/data/llms/llms-full.txt +125291 -0
  193. package/data/llms/llms.txt +379 -0
  194. package/data/llms/manifest.json +12409 -0
  195. package/data/llms/mcp-data.json +14481 -0
  196. package/data/llms/primitive/add-ons/introduction.md +16 -0
  197. package/data/llms/primitive/components/accordion/api.json +1411 -0
  198. package/data/llms/primitive/components/accordion.md +231 -0
  199. package/data/llms/primitive/components/animateonscroll/api.json +193 -0
  200. package/data/llms/primitive/components/animateonscroll.md +91 -0
  201. package/data/llms/primitive/components/autocomplete/api.json +3167 -0
  202. package/data/llms/primitive/components/autocomplete.md +344 -0
  203. package/data/llms/primitive/components/avatar/api.json +703 -0
  204. package/data/llms/primitive/components/avatar.md +134 -0
  205. package/data/llms/primitive/components/badge/api.json +363 -0
  206. package/data/llms/primitive/components/badge.md +118 -0
  207. package/data/llms/primitive/components/breadcrumb/api.json +1162 -0
  208. package/data/llms/primitive/components/breadcrumb.md +196 -0
  209. package/data/llms/primitive/components/button/api.json +246 -0
  210. package/data/llms/primitive/components/button.md +114 -0
  211. package/data/llms/primitive/components/buttongroup/api.json +168 -0
  212. package/data/llms/primitive/components/buttongroup.md +43 -0
  213. package/data/llms/primitive/components/card/api.json +1252 -0
  214. package/data/llms/primitive/components/card.md +159 -0
  215. package/data/llms/primitive/components/carousel/api.json +1374 -0
  216. package/data/llms/primitive/components/carousel.md +220 -0
  217. package/data/llms/primitive/components/checkbox/api.json +697 -0
  218. package/data/llms/primitive/components/checkbox.md +160 -0
  219. package/data/llms/primitive/components/checkboxgroup/api.json +252 -0
  220. package/data/llms/primitive/components/checkboxgroup.md +58 -0
  221. package/data/llms/primitive/components/chip/api.json +853 -0
  222. package/data/llms/primitive/components/chip.md +135 -0
  223. package/data/llms/primitive/components/collapsible/api.json +581 -0
  224. package/data/llms/primitive/components/collapsible.md +133 -0
  225. package/data/llms/primitive/components/compare/api.json +909 -0
  226. package/data/llms/primitive/components/compare.md +156 -0
  227. package/data/llms/primitive/components/contextmenu/api.json +3454 -0
  228. package/data/llms/primitive/components/contextmenu.md +483 -0
  229. package/data/llms/primitive/components/datatable/api.json +7832 -0
  230. package/data/llms/primitive/components/datatable.md +561 -0
  231. package/data/llms/primitive/components/dataview/api.json +819 -0
  232. package/data/llms/primitive/components/dataview.md +163 -0
  233. package/data/llms/primitive/components/datepicker/api.json +9055 -0
  234. package/data/llms/primitive/components/datepicker.md +760 -0
  235. package/data/llms/primitive/components/dialog/api.json +2491 -0
  236. package/data/llms/primitive/components/dialog.md +276 -0
  237. package/data/llms/primitive/components/divider/api.json +214 -0
  238. package/data/llms/primitive/components/divider.md +96 -0
  239. package/data/llms/primitive/components/drawer/api.json +1879 -0
  240. package/data/llms/primitive/components/drawer.md +220 -0
  241. package/data/llms/primitive/components/fieldset/api.json +1116 -0
  242. package/data/llms/primitive/components/fieldset.md +183 -0
  243. package/data/llms/primitive/components/fileupload/api.json +2445 -0
  244. package/data/llms/primitive/components/fileupload.md +282 -0
  245. package/data/llms/primitive/components/floatlabel/api.json +176 -0
  246. package/data/llms/primitive/components/floatlabel.md +47 -0
  247. package/data/llms/primitive/components/focustrap/api.json +241 -0
  248. package/data/llms/primitive/components/focustrap.md +93 -0
  249. package/data/llms/primitive/components/gallery/api.json +3622 -0
  250. package/data/llms/primitive/components/gallery.md +387 -0
  251. package/data/llms/primitive/components/iconfield/api.json +322 -0
  252. package/data/llms/primitive/components/iconfield.md +53 -0
  253. package/data/llms/primitive/components/iftalabel/api.json +177 -0
  254. package/data/llms/primitive/components/iftalabel.md +47 -0
  255. package/data/llms/primitive/components/inplace/api.json +783 -0
  256. package/data/llms/primitive/components/inplace.md +141 -0
  257. package/data/llms/primitive/components/inputcolor/api.json +2211 -0
  258. package/data/llms/primitive/components/inputcolor.md +295 -0
  259. package/data/llms/primitive/components/inputnumber/api.json +469 -0
  260. package/data/llms/primitive/components/inputnumber.md +99 -0
  261. package/data/llms/primitive/components/inputotp/api.json +469 -0
  262. package/data/llms/primitive/components/inputotp.md +121 -0
  263. package/data/llms/primitive/components/inputpassword/api.json +278 -0
  264. package/data/llms/primitive/components/inputpassword.md +84 -0
  265. package/data/llms/primitive/components/inputtags/api.json +407 -0
  266. package/data/llms/primitive/components/inputtags.md +243 -0
  267. package/data/llms/primitive/components/inputtext/api.json +207 -0
  268. package/data/llms/primitive/components/inputtext.md +81 -0
  269. package/data/llms/primitive/components/knob/api.json +841 -0
  270. package/data/llms/primitive/components/knob.md +148 -0
  271. package/data/llms/primitive/components/label/api.json +177 -0
  272. package/data/llms/primitive/components/label.md +44 -0
  273. package/data/llms/primitive/components/listbox/api.json +1741 -0
  274. package/data/llms/primitive/components/listbox.md +206 -0
  275. package/data/llms/primitive/components/menu/api.json +3836 -0
  276. package/data/llms/primitive/components/menu.md +479 -0
  277. package/data/llms/primitive/components/message/api.json +863 -0
  278. package/data/llms/primitive/components/message.md +151 -0
  279. package/data/llms/primitive/components/metergroup/api.json +1222 -0
  280. package/data/llms/primitive/components/metergroup.md +209 -0
  281. package/data/llms/primitive/components/motion.md +112 -0
  282. package/data/llms/primitive/components/navigationmenu/api.json +298 -0
  283. package/data/llms/primitive/components/navigationmenu.md +184 -0
  284. package/data/llms/primitive/components/organizationchart.md +133 -0
  285. package/data/llms/primitive/components/paginator/api.json +1637 -0
  286. package/data/llms/primitive/components/paginator.md +223 -0
  287. package/data/llms/primitive/components/panel/api.json +1270 -0
  288. package/data/llms/primitive/components/panel.md +186 -0
  289. package/data/llms/primitive/components/popover/api.json +2281 -0
  290. package/data/llms/primitive/components/popover.md +277 -0
  291. package/data/llms/primitive/components/portal/api.json +176 -0
  292. package/data/llms/primitive/components/portal.md +79 -0
  293. package/data/llms/primitive/components/progressbar/api.json +857 -0
  294. package/data/llms/primitive/components/progressbar.md +153 -0
  295. package/data/llms/primitive/components/progressspinner/api.json +725 -0
  296. package/data/llms/primitive/components/progressspinner.md +133 -0
  297. package/data/llms/primitive/components/radiobutton/api.json +929 -0
  298. package/data/llms/primitive/components/radiobutton.md +201 -0
  299. package/data/llms/primitive/components/rating/api.json +878 -0
  300. package/data/llms/primitive/components/rating.md +160 -0
  301. package/data/llms/primitive/components/scrollarea/api.json +1187 -0
  302. package/data/llms/primitive/components/scrollarea.md +255 -0
  303. package/data/llms/primitive/components/select/api.json +3167 -0
  304. package/data/llms/primitive/components/select.md +351 -0
  305. package/data/llms/primitive/components/sidebar/api.json +3699 -0
  306. package/data/llms/primitive/components/sidebar.md +444 -0
  307. package/data/llms/primitive/components/skeleton/api.json +213 -0
  308. package/data/llms/primitive/components/skeleton.md +80 -0
  309. package/data/llms/primitive/components/slider/api.json +1066 -0
  310. package/data/llms/primitive/components/slider.md +161 -0
  311. package/data/llms/primitive/components/speeddial/api.json +1002 -0
  312. package/data/llms/primitive/components/speeddial.md +197 -0
  313. package/data/llms/primitive/components/splitter/api.json +901 -0
  314. package/data/llms/primitive/components/splitter.md +137 -0
  315. package/data/llms/primitive/components/stepper/api.json +2102 -0
  316. package/data/llms/primitive/components/stepper.md +253 -0
  317. package/data/llms/primitive/components/styleclass.md +399 -0
  318. package/data/llms/primitive/components/tabs/api.json +1705 -0
  319. package/data/llms/primitive/components/tabs.md +200 -0
  320. package/data/llms/primitive/components/tag/api.json +189 -0
  321. package/data/llms/primitive/components/tag.md +100 -0
  322. package/data/llms/primitive/components/terminal/api.json +1709 -0
  323. package/data/llms/primitive/components/terminal.md +227 -0
  324. package/data/llms/primitive/components/textarea/api.json +213 -0
  325. package/data/llms/primitive/components/textarea.md +83 -0
  326. package/data/llms/primitive/components/timeline/api.json +1180 -0
  327. package/data/llms/primitive/components/timeline.md +185 -0
  328. package/data/llms/primitive/components/toast/api.json +2102 -0
  329. package/data/llms/primitive/components/toast.md +291 -0
  330. package/data/llms/primitive/components/togglebutton/api.json +433 -0
  331. package/data/llms/primitive/components/togglebutton.md +161 -0
  332. package/data/llms/primitive/components/togglebuttongroup/api.json +276 -0
  333. package/data/llms/primitive/components/togglebuttongroup.md +61 -0
  334. package/data/llms/primitive/components/toggleswitch/api.json +681 -0
  335. package/data/llms/primitive/components/toggleswitch.md +149 -0
  336. package/data/llms/primitive/components/toolbar/api.json +646 -0
  337. package/data/llms/primitive/components/toolbar.md +126 -0
  338. package/data/llms/primitive/components/tooltip/api.json +1534 -0
  339. package/data/llms/primitive/components/tooltip.md +202 -0
  340. package/data/llms/primitive/components/tree/api.json +2952 -0
  341. package/data/llms/primitive/components/tree.md +292 -0
  342. package/data/llms/primitive/components/treetable.md +187 -0
  343. package/data/llms/primitive/components/visuallyhidden/api.json +186 -0
  344. package/data/llms/primitive/components/visuallyhidden.md +87 -0
  345. package/data/llms/primitive/guides/ai/llms.md +25 -0
  346. package/data/llms/primitive/guides/icons/customicons.md +47 -0
  347. package/data/llms/primitive/guides/icons/primeicons.md +112 -0
  348. package/data/llms/primitive/guides/installation.md +115 -0
  349. package/data/llms/primitive/guides/introduction.md +20 -0
  350. package/data/llms/primitive/guides/misc/accessibility.md +215 -0
  351. package/data/llms/primitive/guides/misc/passthrough.md +95 -0
  352. package/data/llms/primitive/guides/misc/rtl.md +27 -0
  353. package/data/llms/primitive/llms.md +25 -0
  354. package/data/llms/primitive/resources/introduction.md +15 -0
  355. package/data/llms/styled/add-ons/introduction.md +16 -0
  356. package/data/llms/styled/components/accordion.md +735 -0
  357. package/data/llms/styled/components/animateonscroll.md +264 -0
  358. package/data/llms/styled/components/autocomplete.md +1597 -0
  359. package/data/llms/styled/components/avatar.md +313 -0
  360. package/data/llms/styled/components/badge.md +213 -0
  361. package/data/llms/styled/components/breadcrumb.md +461 -0
  362. package/data/llms/styled/components/button/api.json +246 -0
  363. package/data/llms/styled/components/button.md +641 -0
  364. package/data/llms/styled/components/buttongroup.md +614 -0
  365. package/data/llms/styled/components/card.md +228 -0
  366. package/data/llms/styled/components/carousel.md +388 -0
  367. package/data/llms/styled/components/checkbox.md +329 -0
  368. package/data/llms/styled/components/checkboxgroup.md +619 -0
  369. package/data/llms/styled/components/chip.md +300 -0
  370. package/data/llms/styled/components/compare.md +271 -0
  371. package/data/llms/styled/components/contextmenu.md +550 -0
  372. package/data/llms/styled/components/datatable.md +8259 -0
  373. package/data/llms/styled/components/dataview.md +907 -0
  374. package/data/llms/styled/components/datepicker.md +2807 -0
  375. package/data/llms/styled/components/dialog.md +1047 -0
  376. package/data/llms/styled/components/divider.md +207 -0
  377. package/data/llms/styled/components/drawer.md +470 -0
  378. package/data/llms/styled/components/fieldset.md +334 -0
  379. package/data/llms/styled/components/fileupload.md +747 -0
  380. package/data/llms/styled/components/floatlabel/api.json +210 -0
  381. package/data/llms/styled/components/floatlabel.md +231 -0
  382. package/data/llms/styled/components/fluid/api.json +204 -0
  383. package/data/llms/styled/components/fluid.md +177 -0
  384. package/data/llms/styled/components/focustrap.md +203 -0
  385. package/data/llms/styled/components/gallery.md +553 -0
  386. package/data/llms/styled/components/iconfield/api.json +382 -0
  387. package/data/llms/styled/components/iconfield.md +385 -0
  388. package/data/llms/styled/components/iftalabel/api.json +237 -0
  389. package/data/llms/styled/components/iftalabel.md +158 -0
  390. package/data/llms/styled/components/inplace.md +312 -0
  391. package/data/llms/styled/components/inputcolor.md +674 -0
  392. package/data/llms/styled/components/inputgroup/api.json +399 -0
  393. package/data/llms/styled/components/inputgroup.md +604 -0
  394. package/data/llms/styled/components/inputmask.md +282 -0
  395. package/data/llms/styled/components/inputnumber.md +563 -0
  396. package/data/llms/styled/components/inputotp.md +335 -0
  397. package/data/llms/styled/components/inputpassword.md +630 -0
  398. package/data/llms/styled/components/inputtags.md +727 -0
  399. package/data/llms/styled/components/inputtext.md +190 -0
  400. package/data/llms/styled/components/keyfilter.md +289 -0
  401. package/data/llms/styled/components/knob.md +305 -0
  402. package/data/llms/styled/components/label/api.json +237 -0
  403. package/data/llms/styled/components/label.md +224 -0
  404. package/data/llms/styled/components/listbox.md +807 -0
  405. package/data/llms/styled/components/menu.md +1175 -0
  406. package/data/llms/styled/components/message.md +570 -0
  407. package/data/llms/styled/components/metergroup.md +449 -0
  408. package/data/llms/styled/components/navigationmenu.md +926 -0
  409. package/data/llms/styled/components/orderlist.md +519 -0
  410. package/data/llms/styled/components/organizationchart/api.json +1457 -0
  411. package/data/llms/styled/components/organizationchart.md +791 -0
  412. package/data/llms/styled/components/overlaymanager.md +519 -0
  413. package/data/llms/styled/components/paginator.md +506 -0
  414. package/data/llms/styled/components/panel.md +394 -0
  415. package/data/llms/styled/components/picklist.md +1004 -0
  416. package/data/llms/styled/components/popover.md +372 -0
  417. package/data/llms/styled/components/progressbar.md +438 -0
  418. package/data/llms/styled/components/progressspinner.md +128 -0
  419. package/data/llms/styled/components/radiobutton.md +488 -0
  420. package/data/llms/styled/components/rating/api.json +766 -0
  421. package/data/llms/styled/components/rating.md +518 -0
  422. package/data/llms/styled/components/scrollarea.md +627 -0
  423. package/data/llms/styled/components/select.md +2122 -0
  424. package/data/llms/styled/components/sidebar.md +1587 -0
  425. package/data/llms/styled/components/skeleton.md +226 -0
  426. package/data/llms/styled/components/slider.md +453 -0
  427. package/data/llms/styled/components/speeddial.md +723 -0
  428. package/data/llms/styled/components/splitter.md +536 -0
  429. package/data/llms/styled/components/stepper.md +614 -0
  430. package/data/llms/styled/components/tabs.md +543 -0
  431. package/data/llms/styled/components/tag.md +263 -0
  432. package/data/llms/styled/components/terminal.md +543 -0
  433. package/data/llms/styled/components/textarea.md +257 -0
  434. package/data/llms/styled/components/timeline.md +620 -0
  435. package/data/llms/styled/components/toast.md +1013 -0
  436. package/data/llms/styled/components/togglebutton.md +287 -0
  437. package/data/llms/styled/components/togglebuttongroup.md +328 -0
  438. package/data/llms/styled/components/toggleswitch.md +262 -0
  439. package/data/llms/styled/components/toolbar.md +187 -0
  440. package/data/llms/styled/components/tooltip.md +430 -0
  441. package/data/llms/styled/components/tree.md +1386 -0
  442. package/data/llms/styled/components/treetable.md +6842 -0
  443. package/data/llms/styled/guides/ai/llms.md +25 -0
  444. package/data/llms/styled/guides/configuration.md +151 -0
  445. package/data/llms/styled/guides/icons/customicons.md +47 -0
  446. package/data/llms/styled/guides/icons/primeicons.md +112 -0
  447. package/data/llms/styled/guides/installation/astro.md +97 -0
  448. package/data/llms/styled/guides/installation/manual.md +3 -0
  449. package/data/llms/styled/guides/installation/nextjs.md +129 -0
  450. package/data/llms/styled/guides/installation/react-router.md +88 -0
  451. package/data/llms/styled/guides/installation/tanstack.md +92 -0
  452. package/data/llms/styled/guides/installation/vite.md +91 -0
  453. package/data/llms/styled/guides/installation.md +42 -0
  454. package/data/llms/styled/guides/introduction.md +20 -0
  455. package/data/llms/styled/guides/misc/accessibility.md +211 -0
  456. package/data/llms/styled/guides/misc/animations.md +389 -0
  457. package/data/llms/styled/guides/misc/passthrough.md +95 -0
  458. package/data/llms/styled/guides/misc/rtl.md +27 -0
  459. package/data/llms/styled/guides/theming/styled.md +779 -0
  460. package/data/llms/styled/guides/theming/tailwind.md +473 -0
  461. package/data/llms/styled/guides/theming/unstyled.md +115 -0
  462. package/data/llms/styled/resources/introduction.md +15 -0
  463. package/data/llms/tailwind/add-ons/introduction.md +16 -0
  464. package/data/llms/tailwind/components/accordion.md +383 -0
  465. package/data/llms/tailwind/components/autocomplete.md +1346 -0
  466. package/data/llms/tailwind/components/avatar.md +305 -0
  467. package/data/llms/tailwind/components/badge.md +207 -0
  468. package/data/llms/tailwind/components/breadcrumb.md +387 -0
  469. package/data/llms/tailwind/components/button/api.json +246 -0
  470. package/data/llms/tailwind/components/button.md +617 -0
  471. package/data/llms/tailwind/components/buttongroup.md +599 -0
  472. package/data/llms/tailwind/components/card.md +224 -0
  473. package/data/llms/tailwind/components/carousel.md +329 -0
  474. package/data/llms/tailwind/components/checkbox.md +179 -0
  475. package/data/llms/tailwind/components/checkboxgroup.md +279 -0
  476. package/data/llms/tailwind/components/chip.md +304 -0
  477. package/data/llms/tailwind/components/compare.md +295 -0
  478. package/data/llms/tailwind/components/contextmenu.md +415 -0
  479. package/data/llms/tailwind/components/datatable.md +4062 -0
  480. package/data/llms/tailwind/components/dataview.md +864 -0
  481. package/data/llms/tailwind/components/datepicker.md +823 -0
  482. package/data/llms/tailwind/components/dialog.md +1216 -0
  483. package/data/llms/tailwind/components/divider.md +205 -0
  484. package/data/llms/tailwind/components/drawer.md +500 -0
  485. package/data/llms/tailwind/components/fieldset.md +326 -0
  486. package/data/llms/tailwind/components/fluid.md +117 -0
  487. package/data/llms/tailwind/components/iconfield.md +197 -0
  488. package/data/llms/tailwind/components/inplace.md +324 -0
  489. package/data/llms/tailwind/components/inputcolor.md +493 -0
  490. package/data/llms/tailwind/components/inputgroup.md +375 -0
  491. package/data/llms/tailwind/components/inputnumber.md +463 -0
  492. package/data/llms/tailwind/components/inputotp.md +255 -0
  493. package/data/llms/tailwind/components/inputpassword.md +543 -0
  494. package/data/llms/tailwind/components/inputtext.md +141 -0
  495. package/data/llms/tailwind/components/label.md +100 -0
  496. package/data/llms/tailwind/components/listbox.md +886 -0
  497. package/data/llms/tailwind/components/menu.md +1049 -0
  498. package/data/llms/tailwind/components/message.md +575 -0
  499. package/data/llms/tailwind/components/metergroup.md +510 -0
  500. package/data/llms/tailwind/components/navigationmenu.md +528 -0
  501. package/data/llms/tailwind/components/overlaymanager.md +567 -0
  502. package/data/llms/tailwind/components/paginator.md +306 -0
  503. package/data/llms/tailwind/components/panel.md +395 -0
  504. package/data/llms/tailwind/components/popover.md +424 -0
  505. package/data/llms/tailwind/components/progressbar.md +313 -0
  506. package/data/llms/tailwind/components/radiobutton.md +366 -0
  507. package/data/llms/tailwind/components/rating.md +300 -0
  508. package/data/llms/tailwind/components/scrollarea.md +638 -0
  509. package/data/llms/tailwind/components/select.md +1220 -0
  510. package/data/llms/tailwind/components/sidebar.md +1511 -0
  511. package/data/llms/tailwind/components/skeleton.md +236 -0
  512. package/data/llms/tailwind/components/slider.md +291 -0
  513. package/data/llms/tailwind/components/splitter.md +546 -0
  514. package/data/llms/tailwind/components/tabs.md +462 -0
  515. package/data/llms/tailwind/components/tag.md +255 -0
  516. package/data/llms/tailwind/components/textarea.md +170 -0
  517. package/data/llms/tailwind/components/timeline.md +656 -0
  518. package/data/llms/tailwind/components/toast.md +246 -0
  519. package/data/llms/tailwind/components/togglebutton.md +264 -0
  520. package/data/llms/tailwind/components/togglebuttongroup.md +262 -0
  521. package/data/llms/tailwind/components/toggleswitch.md +203 -0
  522. package/data/llms/tailwind/components/toolbar.md +192 -0
  523. package/data/llms/tailwind/components/tooltip.md +449 -0
  524. package/data/llms/tailwind/guides/ai/llms.md +25 -0
  525. package/data/llms/tailwind/guides/icons/customicons.md +47 -0
  526. package/data/llms/tailwind/guides/icons/primeicons.md +112 -0
  527. package/data/llms/tailwind/guides/installation/astro.md +165 -0
  528. package/data/llms/tailwind/guides/installation/manual.md +3 -0
  529. package/data/llms/tailwind/guides/installation/nextjs.md +169 -0
  530. package/data/llms/tailwind/guides/installation/react-router.md +157 -0
  531. package/data/llms/tailwind/guides/installation/tanstack.md +161 -0
  532. package/data/llms/tailwind/guides/installation/vite.md +160 -0
  533. package/data/llms/tailwind/guides/installation.md +42 -0
  534. package/data/llms/tailwind/guides/introduction.md +20 -0
  535. package/data/llms/tailwind/guides/misc/accessibility.md +205 -0
  536. package/data/llms/tailwind/guides/misc/passthrough.md +95 -0
  537. package/data/llms/tailwind/guides/misc/rtl.md +27 -0
  538. package/data/llms/tailwind/guides/theming/guide.md +179 -0
  539. package/data/llms/tailwind/guides/theming.md +191 -0
  540. package/data/llms/tailwind/llms.md +25 -0
  541. package/data/llms/tailwind/resources/introduction.md +15 -0
  542. package/data/manifest.json +12409 -0
  543. package/data/mcp-data.json +14481 -0
  544. package/dist/index.d.ts +9 -0
  545. package/dist/index.js +2 -0
  546. package/package.json +61 -0
@@ -0,0 +1,4062 @@
1
+ # DataTable
2
+
3
+ DataTable displays tabular data with sorting, pagination, selection and filtering features.
4
+
5
+ ```tsx
6
+ 'use client';
7
+ import {
8
+ DataTable,
9
+ DataTableCell,
10
+ DataTableEmptyTBody,
11
+ DataTableRow,
12
+ DataTableTBody,
13
+ DataTableTHead,
14
+ DataTableTHeadCell,
15
+ DataTableTHeadRow,
16
+ DataTableTHeadTitle,
17
+ DataTableTable,
18
+ DataTableTableContainer
19
+ } from '@/components/ui/datatable';
20
+ import { Tag } from '@/components/ui/tag';
21
+ import { ProductService, type Product } from '@/shared/services/product.service';
22
+ import { Database } from '@primeicons/react/database';
23
+ import Image from 'next/image';
24
+ import * as React from 'react';
25
+
26
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
27
+ INSTOCK: 'success',
28
+ LOWSTOCK: 'warn',
29
+ OUTOFSTOCK: 'danger'
30
+ };
31
+
32
+ const statusLabel: Record<string, string> = {
33
+ INSTOCK: 'In Stock',
34
+ LOWSTOCK: 'Low Stock',
35
+ OUTOFSTOCK: 'Out of Stock'
36
+ };
37
+
38
+ export default function Preview() {
39
+ const [products, setProducts] = React.useState<Product[]>([]);
40
+
41
+ React.useEffect(() => {
42
+ setProducts(ProductService.getProductsData().slice(0, 8));
43
+ }, []);
44
+
45
+ return (
46
+ <div className="w-full">
47
+ <DataTable data={products}>
48
+ <DataTableTableContainer>
49
+ <DataTableTable style={{ minWidth: '50rem' }}>
50
+ <DataTableTHead>
51
+ <DataTableTHeadRow>
52
+ <DataTableTHeadCell>
53
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
54
+ </DataTableTHeadCell>
55
+ <DataTableTHeadCell>
56
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
57
+ </DataTableTHeadCell>
58
+ <DataTableTHeadCell>
59
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
60
+ </DataTableTHeadCell>
61
+ <DataTableTHeadCell>
62
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
63
+ </DataTableTHeadCell>
64
+ </DataTableTHeadRow>
65
+ </DataTableTHead>
66
+ <DataTableTBody>
67
+ {({ item }: { item: any }) => (
68
+ <DataTableRow key={item.id}>
69
+ <DataTableCell>
70
+ <div className="flex items-center gap-3">
71
+ <Image
72
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
73
+ alt={item.name}
74
+ width={48}
75
+ height={48}
76
+ className="rounded-md shadow"
77
+ />
78
+ <div className="flex flex-col">
79
+ <span className="font-medium">{item.name}</span>
80
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
81
+ </div>
82
+ </div>
83
+ </DataTableCell>
84
+ <DataTableCell>
85
+ <Tag severity="secondary">{item.category}</Tag>
86
+ </DataTableCell>
87
+ <DataTableCell>
88
+ <span className="font-semibold">${item.price}</span>
89
+ </DataTableCell>
90
+ <DataTableCell>
91
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
92
+ </DataTableCell>
93
+ </DataTableRow>
94
+ )}
95
+ </DataTableTBody>
96
+ <DataTableEmptyTBody>
97
+ <DataTableRow>
98
+ <DataTableCell colSpan={4}>
99
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
100
+ <div className="relative">
101
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
102
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
103
+ </div>
104
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
105
+ </div>
106
+ <div>
107
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
108
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
109
+ Hang tight, your data will appear here in a moment.
110
+ </p>
111
+ </div>
112
+ </div>
113
+ </DataTableCell>
114
+ </DataTableRow>
115
+ </DataTableEmptyTBody>
116
+ </DataTableTable>
117
+ </DataTableTableContainer>
118
+ </DataTable>
119
+ </div>
120
+ );
121
+ }
122
+ ```
123
+
124
+ ## Installation
125
+
126
+ <DocTabs value="CLI">
127
+ <DocTabsList>
128
+ <DocTabsTab value="CLI">Shadcn CLI</DocTabsTab>
129
+ <DocTabsTab value="Manual">Manual</DocTabsTab>
130
+ </DocTabsList>
131
+ <DocTabsPanel value="CLI">
132
+ ```bash
133
+ npx shadcn@latest add https://primereact.dev/r/datatable.json
134
+ ```
135
+ </DocTabsPanel>
136
+ <DocTabsPanel value="Manual">
137
+ Copy and paste the following code into your project
138
+ <DocSourceViewer name="components/ui/datatable" />
139
+ Update the import paths to match your project setup.
140
+ </DocTabsPanel>
141
+ </DocTabs>
142
+
143
+ ## Usage
144
+
145
+ ```tsx
146
+ import {
147
+ DataTable,
148
+ DataTableCell,
149
+ DataTableHeader,
150
+ DataTableFooter,
151
+ DataTableRow,
152
+ DataTableSort,
153
+ DataTableSortIndicator,
154
+ DataTableSortOrder,
155
+ DataTableTBody,
156
+ DataTableTHead,
157
+ DataTableTHeadCell,
158
+ DataTableTHeadRow,
159
+ DataTableTable,
160
+ DataTableTableContainer,
161
+ DataTablePagination
162
+ } from '@/components/ui/datatable';
163
+ ```
164
+
165
+ ```tsx
166
+ <DataTable data={items}>
167
+ <DataTableTableContainer>
168
+ <DataTableTable>
169
+ <DataTableTHead>
170
+ <DataTableTHeadRow>
171
+ <DataTableTHeadCell>Column</DataTableTHeadCell>
172
+ </DataTableTHeadRow>
173
+ </DataTableTHead>
174
+ <DataTableTBody>
175
+ {({ item }) => (
176
+ <DataTableRow>
177
+ <DataTableCell>{item.name}</DataTableCell>
178
+ </DataTableRow>
179
+ )}
180
+ </DataTableTBody>
181
+ </DataTableTable>
182
+ </DataTableTableContainer>
183
+ </DataTable>
184
+ ```
185
+
186
+ ## Examples
187
+
188
+ ### Basic
189
+
190
+ Displays a list of items in tabular format.
191
+
192
+ ```tsx
193
+ 'use client';
194
+ import {
195
+ DataTable,
196
+ DataTableCell,
197
+ DataTableRow,
198
+ DataTableTBody,
199
+ DataTableTHead,
200
+ DataTableTHeadCell,
201
+ DataTableTHeadRow,
202
+ DataTableTHeadTitle,
203
+ DataTableTable,
204
+ DataTableTableContainer
205
+ } from '@/components/ui/datatable';
206
+ import { Tag } from '@/components/ui/tag';
207
+ import { ProductService, type Product } from '@/shared/services/product.service';
208
+ import Image from 'next/image';
209
+ import * as React from 'react';
210
+
211
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
212
+ INSTOCK: 'success',
213
+ LOWSTOCK: 'warn',
214
+ OUTOFSTOCK: 'danger'
215
+ };
216
+
217
+ const statusLabel: Record<string, string> = {
218
+ INSTOCK: 'In Stock',
219
+ LOWSTOCK: 'Low Stock',
220
+ OUTOFSTOCK: 'Out of Stock'
221
+ };
222
+
223
+ export default function BasicDemo() {
224
+ const [products, setProducts] = React.useState<Product[]>([]);
225
+
226
+ React.useEffect(() => {
227
+ setProducts(ProductService.getProductsData().slice(0, 8));
228
+ }, []);
229
+
230
+ return (
231
+ <div className="w-full">
232
+ <DataTable data={products}>
233
+ <DataTableTableContainer>
234
+ <DataTableTable style={{ minWidth: '50rem' }}>
235
+ <DataTableTHead>
236
+ <DataTableTHeadRow>
237
+ <DataTableTHeadCell>
238
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
239
+ </DataTableTHeadCell>
240
+ <DataTableTHeadCell>
241
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
242
+ </DataTableTHeadCell>
243
+ <DataTableTHeadCell>
244
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
245
+ </DataTableTHeadCell>
246
+ <DataTableTHeadCell>
247
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
248
+ </DataTableTHeadCell>
249
+ </DataTableTHeadRow>
250
+ </DataTableTHead>
251
+ <DataTableTBody>
252
+ {({ item }: { item: any }) => (
253
+ <DataTableRow key={item.id}>
254
+ <DataTableCell>
255
+ <div className="flex items-center gap-3">
256
+ <Image
257
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
258
+ alt={item.name}
259
+ width={40}
260
+ height={40}
261
+ className="rounded-md shadow"
262
+ />
263
+ <div className="flex flex-col">
264
+ <span className="font-medium">{item.name}</span>
265
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
266
+ </div>
267
+ </div>
268
+ </DataTableCell>
269
+ <DataTableCell>
270
+ <Tag severity="secondary">{item.category}</Tag>
271
+ </DataTableCell>
272
+ <DataTableCell>
273
+ <span className="font-semibold">${item.price}</span>
274
+ </DataTableCell>
275
+ <DataTableCell>
276
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
277
+ </DataTableCell>
278
+ </DataTableRow>
279
+ )}
280
+ </DataTableTBody>
281
+ </DataTableTable>
282
+ </DataTableTableContainer>
283
+ </DataTable>
284
+ </div>
285
+ );
286
+ }
287
+ ```
288
+
289
+ ### Striped Rows
290
+
291
+ Use the `stripedRows` prop to render rows with alternating background colors.
292
+
293
+ ```tsx
294
+ 'use client';
295
+ import {
296
+ DataTable,
297
+ DataTableCell,
298
+ DataTableRow,
299
+ DataTableTBody,
300
+ DataTableTHead,
301
+ DataTableTHeadCell,
302
+ DataTableTHeadRow,
303
+ DataTableTable,
304
+ DataTableTableContainer
305
+ } from '@/components/ui/datatable';
306
+ import { Tag } from '@/components/ui/tag';
307
+ import { ProductService, type Product } from '@/shared/services/product.service';
308
+ import * as React from 'react';
309
+
310
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
311
+ INSTOCK: 'success',
312
+ LOWSTOCK: 'warn',
313
+ OUTOFSTOCK: 'danger'
314
+ };
315
+
316
+ const statusLabel: Record<string, string> = {
317
+ INSTOCK: 'In Stock',
318
+ LOWSTOCK: 'Low Stock',
319
+ OUTOFSTOCK: 'Out of Stock'
320
+ };
321
+
322
+ function ProductTable({ stripedRows, showGridlines, size }: { stripedRows?: boolean; showGridlines?: boolean; size?: 'small' | 'large' }) {
323
+ const [products, setProducts] = React.useState<Product[]>([]);
324
+
325
+ React.useEffect(() => {
326
+ setProducts(ProductService.getProductsData().slice(0, 5));
327
+ }, []);
328
+
329
+ return (
330
+ <DataTable data={products} stripedRows={stripedRows} showGridlines={showGridlines} size={size}>
331
+ <DataTableTableContainer>
332
+ <DataTableTable style={{ minWidth: '32rem' }}>
333
+ <DataTableTHead>
334
+ <DataTableTHeadRow>
335
+ <DataTableTHeadCell>Name</DataTableTHeadCell>
336
+ <DataTableTHeadCell>Category</DataTableTHeadCell>
337
+ <DataTableTHeadCell>Price</DataTableTHeadCell>
338
+ <DataTableTHeadCell>Status</DataTableTHeadCell>
339
+ </DataTableTHeadRow>
340
+ </DataTableTHead>
341
+ <DataTableTBody>
342
+ {({ item }: { item: any }) => (
343
+ <DataTableRow key={item.id}>
344
+ <DataTableCell>
345
+ <span className="font-medium">{item.name}</span>
346
+ </DataTableCell>
347
+ <DataTableCell>
348
+ <Tag severity="secondary">{item.category}</Tag>
349
+ </DataTableCell>
350
+ <DataTableCell>
351
+ <span className="font-semibold">${item.price}</span>
352
+ </DataTableCell>
353
+ <DataTableCell>
354
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
355
+ </DataTableCell>
356
+ </DataTableRow>
357
+ )}
358
+ </DataTableTBody>
359
+ </DataTableTable>
360
+ </DataTableTableContainer>
361
+ </DataTable>
362
+ );
363
+ }
364
+
365
+ export default function StripedDemo() {
366
+ return (
367
+ <div className="flex flex-col gap-8 w-full">
368
+ <div>
369
+ <div className="text-xs uppercase tracking-wider text-surface-500 dark:text-surface-400 mb-2">Striped Rows</div>
370
+ <ProductTable stripedRows />
371
+ </div>
372
+ <div>
373
+ <div className="text-xs uppercase tracking-wider text-surface-500 dark:text-surface-400 mb-2">Gridlines</div>
374
+ <ProductTable showGridlines />
375
+ </div>
376
+ <div>
377
+ <div className="text-xs uppercase tracking-wider text-surface-500 dark:text-surface-400 mb-2">Small + Striped + Gridlines</div>
378
+ <ProductTable stripedRows showGridlines size="small" />
379
+ </div>
380
+ <div>
381
+ <div className="text-xs uppercase tracking-wider text-surface-500 dark:text-surface-400 mb-2">Large</div>
382
+ <ProductTable size="large" />
383
+ </div>
384
+ </div>
385
+ );
386
+ }
387
+ ```
388
+
389
+ ### Selection
390
+
391
+ Row selection is driven by the `selectionMode` prop (`single` or `multiple`). The `selectionKeys` prop holds a map of selected row keys and `onSelectionChange` fires when the selection changes.
392
+
393
+ #### Single
394
+
395
+ One row at a time. Clicking a different row replaces the previous selection.
396
+
397
+ ```tsx
398
+ 'use client';
399
+ import Image from 'next/image';
400
+ import { Database } from '@primeicons/react/database';
401
+ import { ProductService, type Product } from '@/shared/services/product.service';
402
+ import type { DataTableSelectionEvent } from 'primereact/datatable';
403
+ import {
404
+ DataTable,
405
+ DataTableCell,
406
+ DataTableEmptyTBody,
407
+ DataTableRow,
408
+ DataTableTBody,
409
+ DataTableTHead,
410
+ DataTableTHeadCell,
411
+ DataTableTHeadRow,
412
+ DataTableTHeadTitle,
413
+ DataTableTable,
414
+ DataTableTableContainer
415
+ } from '@/components/ui/datatable';
416
+ import { Tag } from '@/components/ui/tag';
417
+ import * as React from 'react';
418
+
419
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
420
+ INSTOCK: 'success',
421
+ LOWSTOCK: 'warn',
422
+ OUTOFSTOCK: 'danger'
423
+ };
424
+
425
+ const statusLabel: Record<string, string> = {
426
+ INSTOCK: 'In Stock',
427
+ LOWSTOCK: 'Low Stock',
428
+ OUTOFSTOCK: 'Out of Stock'
429
+ };
430
+
431
+ export default function SingleSelectionDemo() {
432
+ const [products, setProducts] = React.useState<Product[]>([]);
433
+ const [selectionKeys, setSelectionKeys] = React.useState<Record<string, boolean>>({});
434
+
435
+ React.useEffect(() => {
436
+ setProducts(ProductService.getProductsData().slice(0, 8));
437
+ }, []);
438
+
439
+ return (
440
+ <div className="w-full">
441
+ <DataTable
442
+ data={products}
443
+ dataKey="id"
444
+ selectionMode="single"
445
+ selectionKeys={selectionKeys}
446
+ onSelectionChange={(e: DataTableSelectionEvent) => setSelectionKeys(e.value)}
447
+ >
448
+ <DataTableTableContainer>
449
+ <DataTableTable style={{ minWidth: '50rem' }}>
450
+ <DataTableTHead>
451
+ <DataTableTHeadRow>
452
+ <DataTableTHeadCell>
453
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
454
+ </DataTableTHeadCell>
455
+ <DataTableTHeadCell>
456
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
457
+ </DataTableTHeadCell>
458
+ <DataTableTHeadCell>
459
+ <DataTableTHeadTitle>Qty</DataTableTHeadTitle>
460
+ </DataTableTHeadCell>
461
+ <DataTableTHeadCell>
462
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
463
+ </DataTableTHeadCell>
464
+ <DataTableTHeadCell>
465
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
466
+ </DataTableTHeadCell>
467
+ </DataTableTHeadRow>
468
+ </DataTableTHead>
469
+ <DataTableTBody>
470
+ {({ item }: { item: any }) => (
471
+ <DataTableRow key={item.id}>
472
+ <DataTableCell>
473
+ <div className="flex items-center gap-3">
474
+ <Image
475
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
476
+ alt={item.name}
477
+ width={40}
478
+ height={40}
479
+ className="rounded-md shadow"
480
+ />
481
+ <div className="flex flex-col">
482
+ <span className="font-medium">{item.name}</span>
483
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
484
+ </div>
485
+ </div>
486
+ </DataTableCell>
487
+ <DataTableCell>
488
+ <Tag severity="secondary">{item.category}</Tag>
489
+ </DataTableCell>
490
+ <DataTableCell>{item.quantity}</DataTableCell>
491
+ <DataTableCell>
492
+ <span className="font-semibold">${item.price}</span>
493
+ </DataTableCell>
494
+ <DataTableCell>
495
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
496
+ </DataTableCell>
497
+ </DataTableRow>
498
+ )}
499
+ </DataTableTBody>
500
+ <DataTableEmptyTBody>
501
+ <DataTableRow>
502
+ <DataTableCell colSpan={5}>
503
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
504
+ <div className="relative">
505
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
506
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
507
+ </div>
508
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
509
+ </div>
510
+ <div>
511
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
512
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
513
+ Hang tight, your data will appear here in a moment.
514
+ </p>
515
+ </div>
516
+ </div>
517
+ </DataTableCell>
518
+ </DataTableRow>
519
+ </DataTableEmptyTBody>
520
+ </DataTableTable>
521
+ </DataTableTableContainer>
522
+ </DataTable>
523
+ </div>
524
+ );
525
+ }
526
+ ```
527
+
528
+ #### Multiple
529
+
530
+ Multiple rows without a dedicated column. Pair with `metaKeySelection` so Ctrl/Cmd + Click toggles rows and Shift + Click selects a range.
531
+
532
+ ```tsx
533
+ 'use client';
534
+ import Image from 'next/image';
535
+ import { ProductService, type Product } from '@/shared/services/product.service';
536
+ import { ShoppingCart } from '@primeicons/react/shopping-cart';
537
+ import { Database } from '@primeicons/react/database';
538
+ import { Trash } from '@primeicons/react/trash';
539
+ import type { DataTableSelectionEvent } from 'primereact/datatable';
540
+ import { Badge } from '@/components/ui/badge';
541
+ import { Button } from '@/components/ui/button';
542
+ import {
543
+ DataTable,
544
+ DataTableCell,
545
+ DataTableEmptyTBody,
546
+ DataTableRow,
547
+ DataTableTBody,
548
+ DataTableTHead,
549
+ DataTableTHeadCell,
550
+ DataTableTHeadRow,
551
+ DataTableTHeadTitle,
552
+ DataTableTable,
553
+ DataTableTableContainer
554
+ } from '@/components/ui/datatable';
555
+ import { Tag } from '@/components/ui/tag';
556
+ import * as React from 'react';
557
+
558
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
559
+ INSTOCK: 'success',
560
+ LOWSTOCK: 'warn',
561
+ OUTOFSTOCK: 'danger'
562
+ };
563
+
564
+ const statusLabel: Record<string, string> = {
565
+ INSTOCK: 'In Stock',
566
+ LOWSTOCK: 'Low Stock',
567
+ OUTOFSTOCK: 'Out of Stock'
568
+ };
569
+
570
+ export default function MultipleSelectionDemo() {
571
+ const [products, setProducts] = React.useState<Product[]>([]);
572
+ const [selectionKeys, setSelectionKeys] = React.useState<Record<string, boolean>>({});
573
+
574
+ React.useEffect(() => {
575
+ setProducts(ProductService.getProductsData().slice(0, 8));
576
+ }, []);
577
+
578
+ const selectedIds = React.useMemo(() => Object.keys(selectionKeys).filter((k) => selectionKeys[k]), [selectionKeys]);
579
+
580
+ const totalValue = React.useMemo(
581
+ () => products.filter((p) => selectedIds.includes(p.id)).reduce((sum, p) => sum + p.price, 0),
582
+ [products, selectedIds]
583
+ );
584
+
585
+ return (
586
+ <div className="w-full">
587
+ <div className="flex items-center justify-between gap-3 mb-3 p-3 rounded-md border border-surface-200 dark:border-surface-700 bg-surface-50 dark:bg-surface-900">
588
+ <div className="flex items-center gap-2">
589
+ <span className="font-medium text-sm">Selected</span>
590
+ <Badge severity={selectedIds.length ? 'info' : 'secondary'}>{selectedIds.length}</Badge>
591
+ {selectedIds.length > 0 && (
592
+ <span className="text-xs text-surface-500 dark:text-surface-400">
593
+ Total <span className="font-semibold text-surface-900 dark:text-surface-0">${totalValue}</span>
594
+ </span>
595
+ )}
596
+ </div>
597
+ <div className="flex items-center gap-2">
598
+ <Button variant="outlined" severity="danger" size="small" disabled={!selectedIds.length}>
599
+ <Trash />
600
+ Delete
601
+ </Button>
602
+ <Button size="small" disabled={!selectedIds.length}>
603
+ <ShoppingCart />
604
+ Add to cart
605
+ </Button>
606
+ </div>
607
+ </div>
608
+ <p className="mb-2 text-xs text-surface-500 dark:text-surface-400">
609
+ Click to replace the selection. Ctrl/Cmd + Click to toggle a row, Shift + Click to select a range.
610
+ </p>
611
+ <DataTable
612
+ data={products}
613
+ dataKey="id"
614
+ selectionMode="multiple"
615
+ metaKeySelection
616
+ selectionKeys={selectionKeys}
617
+ onSelectionChange={(e: DataTableSelectionEvent) => setSelectionKeys(e.value)}
618
+ >
619
+ <DataTableTableContainer>
620
+ <DataTableTable style={{ minWidth: '50rem' }}>
621
+ <DataTableTHead>
622
+ <DataTableTHeadRow>
623
+ <DataTableTHeadCell>
624
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
625
+ </DataTableTHeadCell>
626
+ <DataTableTHeadCell>
627
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
628
+ </DataTableTHeadCell>
629
+ <DataTableTHeadCell>
630
+ <DataTableTHeadTitle>Qty</DataTableTHeadTitle>
631
+ </DataTableTHeadCell>
632
+ <DataTableTHeadCell>
633
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
634
+ </DataTableTHeadCell>
635
+ <DataTableTHeadCell>
636
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
637
+ </DataTableTHeadCell>
638
+ </DataTableTHeadRow>
639
+ </DataTableTHead>
640
+ <DataTableTBody>
641
+ {({ item }: { item: any }) => (
642
+ <DataTableRow key={item.id}>
643
+ <DataTableCell>
644
+ <div className="flex items-center gap-3">
645
+ <Image
646
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
647
+ alt={item.name}
648
+ width={40}
649
+ height={40}
650
+ className="rounded-md shadow"
651
+ />
652
+ <div className="flex flex-col">
653
+ <span className="font-medium">{item.name}</span>
654
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
655
+ </div>
656
+ </div>
657
+ </DataTableCell>
658
+ <DataTableCell>
659
+ <Tag severity="secondary">{item.category}</Tag>
660
+ </DataTableCell>
661
+ <DataTableCell>{item.quantity}</DataTableCell>
662
+ <DataTableCell>
663
+ <span className="font-semibold">${item.price}</span>
664
+ </DataTableCell>
665
+ <DataTableCell>
666
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
667
+ </DataTableCell>
668
+ </DataTableRow>
669
+ )}
670
+ </DataTableTBody>
671
+ <DataTableEmptyTBody>
672
+ <DataTableRow>
673
+ <DataTableCell colSpan={5}>
674
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
675
+ <div className="relative">
676
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
677
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
678
+ </div>
679
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
680
+ </div>
681
+ <div>
682
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
683
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
684
+ Hang tight, your data will appear here in a moment.
685
+ </p>
686
+ </div>
687
+ </div>
688
+ </DataTableCell>
689
+ </DataTableRow>
690
+ </DataTableEmptyTBody>
691
+ </DataTableTable>
692
+ </DataTableTableContainer>
693
+ </DataTable>
694
+ </div>
695
+ );
696
+ }
697
+ ```
698
+
699
+ #### Checkbox
700
+
701
+ Checkbox-based multiple selection with a header select-all.
702
+
703
+ ```tsx
704
+ 'use client';
705
+ import { Checkbox } from '@/components/ui/checkbox';
706
+ import {
707
+ DataTable,
708
+ DataTableCell,
709
+ DataTableHeader,
710
+ DataTableRow,
711
+ DataTableTBody,
712
+ DataTableTHead,
713
+ DataTableTHeadCell,
714
+ DataTableTHeadRow,
715
+ DataTableTHeadTitle,
716
+ DataTableTable,
717
+ DataTableTableContainer
718
+ } from '@/components/ui/datatable';
719
+ import { Tag } from '@/components/ui/tag';
720
+ import { CustomerService, type Customer } from '@/shared/services/customer.service';
721
+ import { DataTable as PRDataTable, type DataTableSelectionEvent, type DataTableSelectionInstance } from '@primereact/ui/datatable';
722
+ import type { CheckboxRootChangeEvent } from 'primereact/checkbox';
723
+ import * as React from 'react';
724
+
725
+ const statusSeverity: Record<string, 'success' | 'warn' | 'info' | 'danger' | 'secondary'> = {
726
+ qualified: 'success',
727
+ unqualified: 'danger',
728
+ negotiation: 'warn',
729
+ new: 'info',
730
+ renewal: 'secondary',
731
+ proposal: 'info'
732
+ };
733
+
734
+ export default function CheckboxSelectionDemo() {
735
+ const [customers, setCustomers] = React.useState<Customer[]>([]);
736
+ const [selectionKeys, setSelectionKeys] = React.useState<Record<string, boolean>>({});
737
+
738
+ React.useEffect(() => {
739
+ setCustomers(CustomerService.getData().slice(0, 8));
740
+ }, []);
741
+
742
+ const selectedCount = Object.values(selectionKeys).filter(Boolean).length;
743
+
744
+ return (
745
+ <div className="w-full">
746
+ <DataTable
747
+ data={customers}
748
+ dataKey="id"
749
+ selectionKeys={selectionKeys}
750
+ onSelectionChange={(e: DataTableSelectionEvent) => setSelectionKeys(e.value)}
751
+ >
752
+ <DataTableHeader>
753
+ <div className="flex items-center justify-between">
754
+ <span className="text-sm font-semibold">Customers</span>
755
+ <span className="text-xs text-surface-500 dark:text-surface-400">
756
+ {selectedCount > 0 ? `${selectedCount} selected` : `${customers.length} total`}
757
+ </span>
758
+ </div>
759
+ </DataTableHeader>
760
+ <DataTableTableContainer>
761
+ <DataTableTable style={{ minWidth: '40rem' }}>
762
+ <DataTableTHead>
763
+ <DataTableTHeadRow>
764
+ <DataTableTHeadCell style={{ width: '3rem' }}>
765
+ <PRDataTable.Selection>
766
+ {({ isAllSelected, isSomeSelected, toggleAll }: DataTableSelectionInstance) => (
767
+ <Checkbox
768
+ checked={isAllSelected}
769
+ indeterminate={isSomeSelected && !isAllSelected}
770
+ onCheckedChange={(e: CheckboxRootChangeEvent) => toggleAll(e.originalEvent)}
771
+ />
772
+ )}
773
+ </PRDataTable.Selection>
774
+ </DataTableTHeadCell>
775
+ <DataTableTHeadCell>
776
+ <DataTableTHeadTitle>Name</DataTableTHeadTitle>
777
+ </DataTableTHeadCell>
778
+ <DataTableTHeadCell>
779
+ <DataTableTHeadTitle>Country</DataTableTHeadTitle>
780
+ </DataTableTHeadCell>
781
+ <DataTableTHeadCell>
782
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
783
+ </DataTableTHeadCell>
784
+ <DataTableTHeadCell>
785
+ <DataTableTHeadTitle>Balance</DataTableTHeadTitle>
786
+ </DataTableTHeadCell>
787
+ </DataTableTHeadRow>
788
+ </DataTableTHead>
789
+ <DataTableTBody>
790
+ {({ item }: { item: any }) => (
791
+ <DataTableRow key={item.id}>
792
+ <DataTableCell>
793
+ <PRDataTable.Selection>
794
+ {({ isSelected, toggle }: DataTableSelectionInstance) => (
795
+ <Checkbox
796
+ checked={isSelected}
797
+ onCheckedChange={(e: CheckboxRootChangeEvent) => toggle(e.originalEvent)}
798
+ />
799
+ )}
800
+ </PRDataTable.Selection>
801
+ </DataTableCell>
802
+ <DataTableCell>
803
+ <span className="font-medium">{item.name}</span>
804
+ </DataTableCell>
805
+ <DataTableCell>{item.country.name}</DataTableCell>
806
+ <DataTableCell>
807
+ <Tag severity={statusSeverity[item.status] ?? 'secondary'}>{item.status}</Tag>
808
+ </DataTableCell>
809
+ <DataTableCell>
810
+ <span className="font-semibold">${item.balance.toLocaleString()}</span>
811
+ </DataTableCell>
812
+ </DataTableRow>
813
+ )}
814
+ </DataTableTBody>
815
+ </DataTableTable>
816
+ </DataTableTableContainer>
817
+ </DataTable>
818
+ </div>
819
+ );
820
+ }
821
+ ```
822
+
823
+ #### Keyboard
824
+
825
+ Arrow Up/Down moves focus between rows, Space or Enter toggles the focused row, and Shift + Arrow extends a range.
826
+
827
+ ```tsx
828
+ 'use client';
829
+ import Image from 'next/image';
830
+ import { Database } from '@primeicons/react/database';
831
+ import { ProductService, type Product } from '@/shared/services/product.service';
832
+ import type { DataTableSelectionEvent } from 'primereact/datatable';
833
+ import { Badge } from '@/components/ui/badge';
834
+ import {
835
+ DataTable,
836
+ DataTableCell,
837
+ DataTableEmptyTBody,
838
+ DataTableRow,
839
+ DataTableTBody,
840
+ DataTableTHead,
841
+ DataTableTHeadCell,
842
+ DataTableTHeadRow,
843
+ DataTableTHeadTitle,
844
+ DataTableTable,
845
+ DataTableTableContainer
846
+ } from '@/components/ui/datatable';
847
+ import { Tag } from '@/components/ui/tag';
848
+ import * as React from 'react';
849
+
850
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
851
+ INSTOCK: 'success',
852
+ LOWSTOCK: 'warn',
853
+ OUTOFSTOCK: 'danger'
854
+ };
855
+
856
+ const statusLabel: Record<string, string> = {
857
+ INSTOCK: 'In Stock',
858
+ LOWSTOCK: 'Low Stock',
859
+ OUTOFSTOCK: 'Out of Stock'
860
+ };
861
+
862
+ export default function KeyboardDemo() {
863
+ const [products, setProducts] = React.useState<Product[]>([]);
864
+ const [selectionKeys, setSelectionKeys] = React.useState<Record<string, boolean>>({});
865
+
866
+ React.useEffect(() => {
867
+ setProducts(ProductService.getProductsData().slice(0, 8));
868
+ }, []);
869
+
870
+ const selectedCount = Object.values(selectionKeys).filter(Boolean).length;
871
+
872
+ return (
873
+ <div className="w-full">
874
+ <div className="flex items-center justify-between gap-3 mb-3 p-3 rounded-md border border-surface-200 dark:border-surface-700 bg-surface-50 dark:bg-surface-900">
875
+ <span className="text-sm text-surface-500 dark:text-surface-400">
876
+ <kbd className="px-1.5 py-0.5 text-xs rounded bg-surface-200 dark:bg-surface-700">↑</kbd>{' '}
877
+ <kbd className="px-1.5 py-0.5 text-xs rounded bg-surface-200 dark:bg-surface-700">↓</kbd> navigate,{' '}
878
+ <kbd className="px-1.5 py-0.5 text-xs rounded bg-surface-200 dark:bg-surface-700">Space</kbd> /{' '}
879
+ <kbd className="px-1.5 py-0.5 text-xs rounded bg-surface-200 dark:bg-surface-700">Enter</kbd> select,{' '}
880
+ <kbd className="px-1.5 py-0.5 text-xs rounded bg-surface-200 dark:bg-surface-700">Shift + ↑↓</kbd> range
881
+ </span>
882
+ <div className="flex items-center gap-2">
883
+ <span className="text-sm font-medium">Selected</span>
884
+ <Badge severity={selectedCount ? 'info' : 'secondary'}>{selectedCount}</Badge>
885
+ </div>
886
+ </div>
887
+ <DataTable
888
+ data={products}
889
+ dataKey="id"
890
+ selectionMode="multiple"
891
+ metaKeySelection
892
+ selectionKeys={selectionKeys}
893
+ onSelectionChange={(e: DataTableSelectionEvent) => setSelectionKeys(e.value)}
894
+ >
895
+ <DataTableTableContainer>
896
+ <DataTableTable style={{ minWidth: '50rem' }}>
897
+ <DataTableTHead>
898
+ <DataTableTHeadRow>
899
+ <DataTableTHeadCell>
900
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
901
+ </DataTableTHeadCell>
902
+ <DataTableTHeadCell>
903
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
904
+ </DataTableTHeadCell>
905
+ <DataTableTHeadCell>
906
+ <DataTableTHeadTitle>Qty</DataTableTHeadTitle>
907
+ </DataTableTHeadCell>
908
+ <DataTableTHeadCell>
909
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
910
+ </DataTableTHeadCell>
911
+ <DataTableTHeadCell>
912
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
913
+ </DataTableTHeadCell>
914
+ </DataTableTHeadRow>
915
+ </DataTableTHead>
916
+ <DataTableTBody>
917
+ {({ item }: { item: any }) => (
918
+ <DataTableRow key={item.id}>
919
+ <DataTableCell>
920
+ <div className="flex items-center gap-3">
921
+ <Image
922
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
923
+ alt={item.name}
924
+ width={36}
925
+ height={36}
926
+ className="rounded-md shadow"
927
+ />
928
+ <span className="font-medium">{item.name}</span>
929
+ </div>
930
+ </DataTableCell>
931
+ <DataTableCell>
932
+ <Tag severity="secondary">{item.category}</Tag>
933
+ </DataTableCell>
934
+ <DataTableCell>{item.quantity}</DataTableCell>
935
+ <DataTableCell>
936
+ <span className="font-semibold">${item.price}</span>
937
+ </DataTableCell>
938
+ <DataTableCell>
939
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
940
+ </DataTableCell>
941
+ </DataTableRow>
942
+ )}
943
+ </DataTableTBody>
944
+ <DataTableEmptyTBody>
945
+ <DataTableRow>
946
+ <DataTableCell colSpan={5}>
947
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
948
+ <div className="relative">
949
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
950
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
951
+ </div>
952
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
953
+ </div>
954
+ <div>
955
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
956
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
957
+ Hang tight, your data will appear here in a moment.
958
+ </p>
959
+ </div>
960
+ </div>
961
+ </DataTableCell>
962
+ </DataTableRow>
963
+ </DataTableEmptyTBody>
964
+ </DataTableTable>
965
+ </DataTableTableContainer>
966
+ </DataTable>
967
+ </div>
968
+ );
969
+ }
970
+ ```
971
+
972
+ ### Sort
973
+
974
+ Wrap a column header with `DataTableSort field="…"` to make it sortable. `DataTableSortIndicator match="asc | desc | unsorted"` renders direction icons and `DataTableSortOrder` shows the priority badge in multi-sort mode.
975
+
976
+ #### Single
977
+
978
+ Clicking a column header cycles through ascending, descending and unsorted.
979
+
980
+ ```tsx
981
+ 'use client';
982
+ import {
983
+ DataTable,
984
+ DataTableCell,
985
+ DataTableEmptyTBody,
986
+ DataTableRow,
987
+ DataTableSort,
988
+ DataTableSortIndicator,
989
+ DataTableSortOrder,
990
+ DataTableTBody,
991
+ DataTableTHead,
992
+ DataTableTHeadCell,
993
+ DataTableTHeadRow,
994
+ DataTableTHeadTitle,
995
+ DataTableTable,
996
+ DataTableTableContainer
997
+ } from '@/components/ui/datatable';
998
+ import { Badge } from '@/components/ui/badge';
999
+ import { OverlayBadge } from '@/components/ui/overlaybadge';
1000
+ import { Tag } from '@/components/ui/tag';
1001
+ import { ProductService, type Product } from '@/shared/services/product.service';
1002
+ import { Database } from '@primeicons/react/database';
1003
+ import { SortAlt } from '@primeicons/react/sort-alt';
1004
+ import { SortAmountDown } from '@primeicons/react/sort-amount-down';
1005
+ import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt';
1006
+ import type { DataTableSortOrderInstance } from 'primereact/datatable';
1007
+ import Image from 'next/image';
1008
+ import * as React from 'react';
1009
+
1010
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
1011
+ INSTOCK: 'success',
1012
+ LOWSTOCK: 'warn',
1013
+ OUTOFSTOCK: 'danger'
1014
+ };
1015
+
1016
+ const statusLabel: Record<string, string> = {
1017
+ INSTOCK: 'In Stock',
1018
+ LOWSTOCK: 'Low Stock',
1019
+ OUTOFSTOCK: 'Out of Stock'
1020
+ };
1021
+
1022
+ function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) {
1023
+ return (
1024
+ <DataTableSort field={field} className="inline-flex items-center gap-2">
1025
+ {children}
1026
+ <OverlayBadge className="px-1">
1027
+ <DataTableSortIndicator match="unsorted">
1028
+ <SortAlt />
1029
+ </DataTableSortIndicator>
1030
+ <DataTableSortIndicator match="asc">
1031
+ <SortAmountUpAlt />
1032
+ </DataTableSortIndicator>
1033
+ <DataTableSortIndicator match="desc">
1034
+ <SortAmountDown />
1035
+ </DataTableSortIndicator>
1036
+ <DataTableSortOrder asChild>
1037
+ {({ index }: DataTableSortOrderInstance) => (
1038
+ <Badge shape="circle" size="small" severity="info" className="mt-1">
1039
+ {index}
1040
+ </Badge>
1041
+ )}
1042
+ </DataTableSortOrder>
1043
+ </OverlayBadge>
1044
+ </DataTableSort>
1045
+ );
1046
+ }
1047
+
1048
+ export default function SortDemo() {
1049
+ const [products, setProducts] = React.useState<Product[]>([]);
1050
+
1051
+ React.useEffect(() => {
1052
+ setProducts(ProductService.getProductsData().slice(0, 10));
1053
+ }, []);
1054
+
1055
+ return (
1056
+ <div className="w-full">
1057
+ <DataTable data={products} removableSort>
1058
+ <DataTableTableContainer>
1059
+ <DataTableTable style={{ minWidth: '50rem' }}>
1060
+ <DataTableTHead>
1061
+ <DataTableTHeadRow>
1062
+ <DataTableTHeadCell>
1063
+ <SortableHeader field="name">
1064
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
1065
+ </SortableHeader>
1066
+ </DataTableTHeadCell>
1067
+ <DataTableTHeadCell>
1068
+ <SortableHeader field="category">
1069
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
1070
+ </SortableHeader>
1071
+ </DataTableTHeadCell>
1072
+ <DataTableTHeadCell>
1073
+ <SortableHeader field="quantity">
1074
+ <DataTableTHeadTitle>Quantity</DataTableTHeadTitle>
1075
+ </SortableHeader>
1076
+ </DataTableTHeadCell>
1077
+ <DataTableTHeadCell>
1078
+ <SortableHeader field="price">
1079
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
1080
+ </SortableHeader>
1081
+ </DataTableTHeadCell>
1082
+ <DataTableTHeadCell>
1083
+ <SortableHeader field="inventoryStatus">
1084
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
1085
+ </SortableHeader>
1086
+ </DataTableTHeadCell>
1087
+ </DataTableTHeadRow>
1088
+ </DataTableTHead>
1089
+ <DataTableTBody>
1090
+ {({ item }: { item: any }) => (
1091
+ <DataTableRow key={item.id}>
1092
+ <DataTableCell>
1093
+ <div className="flex items-center gap-3">
1094
+ <Image
1095
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
1096
+ alt={item.name}
1097
+ width={40}
1098
+ height={40}
1099
+ className="rounded-md shadow"
1100
+ />
1101
+ <div className="flex flex-col">
1102
+ <span className="font-medium">{item.name}</span>
1103
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
1104
+ </div>
1105
+ </div>
1106
+ </DataTableCell>
1107
+ <DataTableCell>
1108
+ <Tag severity="secondary">{item.category}</Tag>
1109
+ </DataTableCell>
1110
+ <DataTableCell>{item.quantity}</DataTableCell>
1111
+ <DataTableCell>
1112
+ <span className="font-semibold">${item.price}</span>
1113
+ </DataTableCell>
1114
+ <DataTableCell>
1115
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
1116
+ </DataTableCell>
1117
+ </DataTableRow>
1118
+ )}
1119
+ </DataTableTBody>
1120
+ <DataTableEmptyTBody>
1121
+ <DataTableRow>
1122
+ <DataTableCell colSpan={5}>
1123
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
1124
+ <div className="relative">
1125
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
1126
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
1127
+ </div>
1128
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
1129
+ </div>
1130
+ <div>
1131
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
1132
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
1133
+ Hang tight, your data will appear here in a moment.
1134
+ </p>
1135
+ </div>
1136
+ </div>
1137
+ </DataTableCell>
1138
+ </DataTableRow>
1139
+ </DataTableEmptyTBody>
1140
+ </DataTableTable>
1141
+ </DataTableTableContainer>
1142
+ </DataTable>
1143
+ </div>
1144
+ );
1145
+ }
1146
+ ```
1147
+
1148
+ #### Multiple
1149
+
1150
+ Hold Ctrl/Cmd and click multiple column headers to sort by several fields at once.
1151
+
1152
+ ```tsx
1153
+ 'use client';
1154
+ import Image from 'next/image';
1155
+ import { ProductService, type Product } from '@/shared/services/product.service';
1156
+ import type { DataTableSortOrderInstance } from 'primereact/datatable';
1157
+ import { SortAlt } from '@primeicons/react/sort-alt';
1158
+ import { Database } from '@primeicons/react/database';
1159
+ import { SortAmountDown } from '@primeicons/react/sort-amount-down';
1160
+ import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt';
1161
+ import { Badge } from '@/components/ui/badge';
1162
+ import {
1163
+ DataTable,
1164
+ DataTableCell,
1165
+ DataTableEmptyTBody,
1166
+ DataTableRow,
1167
+ DataTableSort,
1168
+ DataTableSortIndicator,
1169
+ DataTableSortOrder,
1170
+ DataTableTBody,
1171
+ DataTableTHead,
1172
+ DataTableTHeadCell,
1173
+ DataTableTHeadRow,
1174
+ DataTableTHeadTitle,
1175
+ DataTableTable,
1176
+ DataTableTableContainer
1177
+ } from '@/components/ui/datatable';
1178
+ import { OverlayBadge } from '@/components/ui/overlaybadge';
1179
+ import { Tag } from '@/components/ui/tag';
1180
+ import * as React from 'react';
1181
+
1182
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
1183
+ INSTOCK: 'success',
1184
+ LOWSTOCK: 'warn',
1185
+ OUTOFSTOCK: 'danger'
1186
+ };
1187
+
1188
+ const statusLabel: Record<string, string> = {
1189
+ INSTOCK: 'In Stock',
1190
+ LOWSTOCK: 'Low Stock',
1191
+ OUTOFSTOCK: 'Out of Stock'
1192
+ };
1193
+
1194
+ function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) {
1195
+ return (
1196
+ <DataTableSort field={field} className="inline-flex items-center gap-2">
1197
+ {children}
1198
+ <OverlayBadge className="px-1">
1199
+ <DataTableSortIndicator match="unsorted">
1200
+ <SortAlt />
1201
+ </DataTableSortIndicator>
1202
+ <DataTableSortIndicator match="asc">
1203
+ <SortAmountUpAlt />
1204
+ </DataTableSortIndicator>
1205
+ <DataTableSortIndicator match="desc">
1206
+ <SortAmountDown />
1207
+ </DataTableSortIndicator>
1208
+ <DataTableSortOrder asChild>
1209
+ {({ index }: DataTableSortOrderInstance) => (
1210
+ <Badge shape="circle" size="small" severity="info" className="mt-1">
1211
+ {index}
1212
+ </Badge>
1213
+ )}
1214
+ </DataTableSortOrder>
1215
+ </OverlayBadge>
1216
+ </DataTableSort>
1217
+ );
1218
+ }
1219
+
1220
+ export default function MultiSortDemo() {
1221
+ const [products, setProducts] = React.useState<Product[]>([]);
1222
+
1223
+ React.useEffect(() => {
1224
+ setProducts(ProductService.getProductsData().slice(0, 10));
1225
+ }, []);
1226
+
1227
+ return (
1228
+ <div className="w-full">
1229
+ <DataTable data={products} removableSort>
1230
+ <DataTableTableContainer>
1231
+ <DataTableTable style={{ minWidth: '50rem' }}>
1232
+ <DataTableTHead>
1233
+ <DataTableTHeadRow>
1234
+ <DataTableTHeadCell>
1235
+ <SortableHeader field="name">
1236
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
1237
+ </SortableHeader>
1238
+ </DataTableTHeadCell>
1239
+ <DataTableTHeadCell>
1240
+ <SortableHeader field="category">
1241
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
1242
+ </SortableHeader>
1243
+ </DataTableTHeadCell>
1244
+ <DataTableTHeadCell>
1245
+ <SortableHeader field="quantity">
1246
+ <DataTableTHeadTitle>Quantity</DataTableTHeadTitle>
1247
+ </SortableHeader>
1248
+ </DataTableTHeadCell>
1249
+ <DataTableTHeadCell>
1250
+ <SortableHeader field="price">
1251
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
1252
+ </SortableHeader>
1253
+ </DataTableTHeadCell>
1254
+ <DataTableTHeadCell>
1255
+ <SortableHeader field="inventoryStatus">
1256
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
1257
+ </SortableHeader>
1258
+ </DataTableTHeadCell>
1259
+ </DataTableTHeadRow>
1260
+ </DataTableTHead>
1261
+ <DataTableTBody>
1262
+ {({ item }: { item: any }) => (
1263
+ <DataTableRow key={item.id}>
1264
+ <DataTableCell>
1265
+ <div className="flex items-center gap-3">
1266
+ <Image
1267
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
1268
+ alt={item.name}
1269
+ width={40}
1270
+ height={40}
1271
+ className="rounded-md shadow"
1272
+ />
1273
+ <div className="flex flex-col">
1274
+ <span className="font-medium">{item.name}</span>
1275
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
1276
+ </div>
1277
+ </div>
1278
+ </DataTableCell>
1279
+ <DataTableCell>
1280
+ <Tag severity="secondary">{item.category}</Tag>
1281
+ </DataTableCell>
1282
+ <DataTableCell>{item.quantity}</DataTableCell>
1283
+ <DataTableCell>
1284
+ <span className="font-semibold">${item.price}</span>
1285
+ </DataTableCell>
1286
+ <DataTableCell>
1287
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
1288
+ </DataTableCell>
1289
+ </DataTableRow>
1290
+ )}
1291
+ </DataTableTBody>
1292
+ <DataTableEmptyTBody>
1293
+ <DataTableRow>
1294
+ <DataTableCell colSpan={5}>
1295
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
1296
+ <div className="relative">
1297
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
1298
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
1299
+ </div>
1300
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
1301
+ </div>
1302
+ <div>
1303
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
1304
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
1305
+ Hang tight, your data will appear here in a moment.
1306
+ </p>
1307
+ </div>
1308
+ </div>
1309
+ </DataTableCell>
1310
+ </DataTableRow>
1311
+ </DataTableEmptyTBody>
1312
+ </DataTableTable>
1313
+ </DataTableTableContainer>
1314
+ </DataTable>
1315
+ </div>
1316
+ );
1317
+ }
1318
+ ```
1319
+
1320
+ #### Presort
1321
+
1322
+ Apply an initial sort on mount via `defaultSortField` + `defaultSortOrder`.
1323
+
1324
+ ```tsx
1325
+ 'use client';
1326
+ import Image from 'next/image';
1327
+ import { ProductService, type Product } from '@/shared/services/product.service';
1328
+ import type { DataTableSortOrderInstance } from 'primereact/datatable';
1329
+ import { SortAlt } from '@primeicons/react/sort-alt';
1330
+ import { Database } from '@primeicons/react/database';
1331
+ import { SortAmountDown } from '@primeicons/react/sort-amount-down';
1332
+ import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt';
1333
+ import { Badge } from '@/components/ui/badge';
1334
+ import {
1335
+ DataTable,
1336
+ DataTableCell,
1337
+ DataTableEmptyTBody,
1338
+ DataTableRow,
1339
+ DataTableSort,
1340
+ DataTableSortIndicator,
1341
+ DataTableSortOrder,
1342
+ DataTableTBody,
1343
+ DataTableTHead,
1344
+ DataTableTHeadCell,
1345
+ DataTableTHeadRow,
1346
+ DataTableTHeadTitle,
1347
+ DataTableTable,
1348
+ DataTableTableContainer
1349
+ } from '@/components/ui/datatable';
1350
+ import { OverlayBadge } from '@/components/ui/overlaybadge';
1351
+ import { Tag } from '@/components/ui/tag';
1352
+ import * as React from 'react';
1353
+
1354
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
1355
+ INSTOCK: 'success',
1356
+ LOWSTOCK: 'warn',
1357
+ OUTOFSTOCK: 'danger'
1358
+ };
1359
+
1360
+ const statusLabel: Record<string, string> = {
1361
+ INSTOCK: 'In Stock',
1362
+ LOWSTOCK: 'Low Stock',
1363
+ OUTOFSTOCK: 'Out of Stock'
1364
+ };
1365
+
1366
+ function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) {
1367
+ return (
1368
+ <DataTableSort field={field} className="inline-flex items-center gap-2">
1369
+ {children}
1370
+ <OverlayBadge className="px-1">
1371
+ <DataTableSortIndicator match="unsorted">
1372
+ <SortAlt />
1373
+ </DataTableSortIndicator>
1374
+ <DataTableSortIndicator match="asc">
1375
+ <SortAmountUpAlt />
1376
+ </DataTableSortIndicator>
1377
+ <DataTableSortIndicator match="desc">
1378
+ <SortAmountDown />
1379
+ </DataTableSortIndicator>
1380
+ <DataTableSortOrder asChild>
1381
+ {({ index }: DataTableSortOrderInstance) => (
1382
+ <Badge shape="circle" size="small" severity="info" className="mt-1">
1383
+ {index}
1384
+ </Badge>
1385
+ )}
1386
+ </DataTableSortOrder>
1387
+ </OverlayBadge>
1388
+ </DataTableSort>
1389
+ );
1390
+ }
1391
+
1392
+ export default function PresortDemo() {
1393
+ const [products, setProducts] = React.useState<Product[]>([]);
1394
+
1395
+ React.useEffect(() => {
1396
+ setProducts(ProductService.getProductsData().slice(0, 10));
1397
+ }, []);
1398
+
1399
+ return (
1400
+ <div className="w-full">
1401
+ <DataTable data={products} defaultSortField="price" defaultSortOrder={-1} removableSort>
1402
+ <DataTableTableContainer>
1403
+ <DataTableTable style={{ minWidth: '50rem' }}>
1404
+ <DataTableTHead>
1405
+ <DataTableTHeadRow>
1406
+ <DataTableTHeadCell>
1407
+ <SortableHeader field="name">
1408
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
1409
+ </SortableHeader>
1410
+ </DataTableTHeadCell>
1411
+ <DataTableTHeadCell>
1412
+ <SortableHeader field="category">
1413
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
1414
+ </SortableHeader>
1415
+ </DataTableTHeadCell>
1416
+ <DataTableTHeadCell>
1417
+ <SortableHeader field="quantity">
1418
+ <DataTableTHeadTitle>Quantity</DataTableTHeadTitle>
1419
+ </SortableHeader>
1420
+ </DataTableTHeadCell>
1421
+ <DataTableTHeadCell>
1422
+ <SortableHeader field="price">
1423
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
1424
+ </SortableHeader>
1425
+ </DataTableTHeadCell>
1426
+ <DataTableTHeadCell>
1427
+ <SortableHeader field="inventoryStatus">
1428
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
1429
+ </SortableHeader>
1430
+ </DataTableTHeadCell>
1431
+ </DataTableTHeadRow>
1432
+ </DataTableTHead>
1433
+ <DataTableTBody>
1434
+ {({ item }: { item: any }) => (
1435
+ <DataTableRow key={item.id}>
1436
+ <DataTableCell>
1437
+ <div className="flex items-center gap-3">
1438
+ <Image
1439
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
1440
+ alt={item.name}
1441
+ width={40}
1442
+ height={40}
1443
+ className="rounded-md shadow"
1444
+ />
1445
+ <div className="flex flex-col">
1446
+ <span className="font-medium">{item.name}</span>
1447
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
1448
+ </div>
1449
+ </div>
1450
+ </DataTableCell>
1451
+ <DataTableCell>
1452
+ <Tag severity="secondary">{item.category}</Tag>
1453
+ </DataTableCell>
1454
+ <DataTableCell>{item.quantity}</DataTableCell>
1455
+ <DataTableCell>
1456
+ <span className="font-semibold">${item.price}</span>
1457
+ </DataTableCell>
1458
+ <DataTableCell>
1459
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
1460
+ </DataTableCell>
1461
+ </DataTableRow>
1462
+ )}
1463
+ </DataTableTBody>
1464
+ <DataTableEmptyTBody>
1465
+ <DataTableRow>
1466
+ <DataTableCell colSpan={5}>
1467
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
1468
+ <div className="relative">
1469
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
1470
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
1471
+ </div>
1472
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
1473
+ </div>
1474
+ <div>
1475
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
1476
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
1477
+ Hang tight, your data will appear here in a moment.
1478
+ </p>
1479
+ </div>
1480
+ </div>
1481
+ </DataTableCell>
1482
+ </DataTableRow>
1483
+ </DataTableEmptyTBody>
1484
+ </DataTableTable>
1485
+ </DataTableTableContainer>
1486
+ </DataTable>
1487
+ </div>
1488
+ );
1489
+ }
1490
+ ```
1491
+
1492
+ For multi-column presort, use `defaultMultiSortMeta`.
1493
+
1494
+ ```tsx
1495
+ 'use client';
1496
+ import { ProductService, type Product } from '@/shared/services/product.service';
1497
+ import type { DataTableSortOrderInstance } from 'primereact/datatable';
1498
+ import { SortAlt } from '@primeicons/react/sort-alt';
1499
+ import { Database } from '@primeicons/react/database';
1500
+ import { SortAmountDown } from '@primeicons/react/sort-amount-down';
1501
+ import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt';
1502
+ import { Badge } from '@/components/ui/badge';
1503
+ import {
1504
+ DataTable,
1505
+ DataTableCell,
1506
+ DataTableEmptyTBody,
1507
+ DataTableRow,
1508
+ DataTableSort,
1509
+ DataTableSortIndicator,
1510
+ DataTableSortOrder,
1511
+ DataTableTBody,
1512
+ DataTableTHead,
1513
+ DataTableTHeadCell,
1514
+ DataTableTHeadRow,
1515
+ DataTableTHeadTitle,
1516
+ DataTableTable,
1517
+ DataTableTableContainer
1518
+ } from '@/components/ui/datatable';
1519
+ import { OverlayBadge } from '@/components/ui/overlaybadge';
1520
+ import { Tag } from '@/components/ui/tag';
1521
+ import Image from 'next/image';
1522
+ import * as React from 'react';
1523
+
1524
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
1525
+ INSTOCK: 'success',
1526
+ LOWSTOCK: 'warn',
1527
+ OUTOFSTOCK: 'danger'
1528
+ };
1529
+
1530
+ const statusLabel: Record<string, string> = {
1531
+ INSTOCK: 'In Stock',
1532
+ LOWSTOCK: 'Low Stock',
1533
+ OUTOFSTOCK: 'Out of Stock'
1534
+ };
1535
+
1536
+ function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) {
1537
+ return (
1538
+ <DataTableSort field={field} className="inline-flex items-center gap-2">
1539
+ {children}
1540
+ <OverlayBadge className="px-1">
1541
+ <DataTableSortIndicator match="unsorted">
1542
+ <SortAlt />
1543
+ </DataTableSortIndicator>
1544
+ <DataTableSortIndicator match="asc">
1545
+ <SortAmountUpAlt />
1546
+ </DataTableSortIndicator>
1547
+ <DataTableSortIndicator match="desc">
1548
+ <SortAmountDown />
1549
+ </DataTableSortIndicator>
1550
+ <DataTableSortOrder asChild>
1551
+ {({ index }: DataTableSortOrderInstance) => (
1552
+ <Badge shape="circle" size="small" severity="info" className="mt-1">
1553
+ {index}
1554
+ </Badge>
1555
+ )}
1556
+ </DataTableSortOrder>
1557
+ </OverlayBadge>
1558
+ </DataTableSort>
1559
+ );
1560
+ }
1561
+
1562
+ export default function PresortMultiDemo() {
1563
+ const [products, setProducts] = React.useState<Product[]>([]);
1564
+
1565
+ React.useEffect(() => {
1566
+ setProducts(ProductService.getProductsData().slice(0, 10));
1567
+ }, []);
1568
+
1569
+ return (
1570
+ <div className="w-full">
1571
+ <DataTable
1572
+ data={products}
1573
+ defaultMultiSortMeta={[
1574
+ { field: 'category', order: 1 },
1575
+ { field: 'price', order: -1 }
1576
+ ]}
1577
+ removableSort
1578
+ >
1579
+ <DataTableTableContainer>
1580
+ <DataTableTable style={{ minWidth: '50rem' }}>
1581
+ <DataTableTHead>
1582
+ <DataTableTHeadRow>
1583
+ <DataTableTHeadCell>
1584
+ <SortableHeader field="name">
1585
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
1586
+ </SortableHeader>
1587
+ </DataTableTHeadCell>
1588
+ <DataTableTHeadCell>
1589
+ <SortableHeader field="category">
1590
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
1591
+ </SortableHeader>
1592
+ </DataTableTHeadCell>
1593
+ <DataTableTHeadCell>
1594
+ <SortableHeader field="quantity">
1595
+ <DataTableTHeadTitle>Quantity</DataTableTHeadTitle>
1596
+ </SortableHeader>
1597
+ </DataTableTHeadCell>
1598
+ <DataTableTHeadCell>
1599
+ <SortableHeader field="price">
1600
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
1601
+ </SortableHeader>
1602
+ </DataTableTHeadCell>
1603
+ <DataTableTHeadCell>
1604
+ <SortableHeader field="inventoryStatus">
1605
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
1606
+ </SortableHeader>
1607
+ </DataTableTHeadCell>
1608
+ </DataTableTHeadRow>
1609
+ </DataTableTHead>
1610
+ <DataTableTBody>
1611
+ {({ item }: { item: any }) => (
1612
+ <DataTableRow key={item.id}>
1613
+ <DataTableCell>
1614
+ <div className="flex items-center gap-3">
1615
+ <Image
1616
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
1617
+ alt={item.name}
1618
+ width={40}
1619
+ height={40}
1620
+ className="rounded-md shadow"
1621
+ />
1622
+ <div className="flex flex-col">
1623
+ <span className="font-medium">{item.name}</span>
1624
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
1625
+ </div>
1626
+ </div>
1627
+ </DataTableCell>
1628
+ <DataTableCell>
1629
+ <Tag severity="secondary">{item.category}</Tag>
1630
+ </DataTableCell>
1631
+ <DataTableCell>{item.quantity}</DataTableCell>
1632
+ <DataTableCell>
1633
+ <span className="font-semibold">${item.price}</span>
1634
+ </DataTableCell>
1635
+ <DataTableCell>
1636
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
1637
+ </DataTableCell>
1638
+ </DataTableRow>
1639
+ )}
1640
+ </DataTableTBody>
1641
+ <DataTableEmptyTBody>
1642
+ <DataTableRow>
1643
+ <DataTableCell colSpan={5}>
1644
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
1645
+ <div className="relative">
1646
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
1647
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
1648
+ </div>
1649
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
1650
+ </div>
1651
+ <div>
1652
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
1653
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
1654
+ Hang tight, your data will appear here in a moment.
1655
+ </p>
1656
+ </div>
1657
+ </div>
1658
+ </DataTableCell>
1659
+ </DataTableRow>
1660
+ </DataTableEmptyTBody>
1661
+ </DataTableTable>
1662
+ </DataTableTableContainer>
1663
+ </DataTable>
1664
+ </div>
1665
+ );
1666
+ }
1667
+ ```
1668
+
1669
+ ### Pagination
1670
+
1671
+ DataTable exposes pagination state via render prop, allowing complete control over the pagination UI.
1672
+
1673
+ ```tsx
1674
+ 'use client';
1675
+ import {
1676
+ DataTable,
1677
+ DataTableCell,
1678
+ DataTablePagination,
1679
+ DataTableRow,
1680
+ DataTableTBody,
1681
+ DataTableTHead,
1682
+ DataTableTHeadCell,
1683
+ DataTableTHeadRow,
1684
+ DataTableTHeadTitle,
1685
+ DataTableTable,
1686
+ DataTableTableContainer
1687
+ } from '@/components/ui/datatable';
1688
+ import { Paginator, PaginatorFirst, PaginatorLast, PaginatorNext, PaginatorPages, PaginatorPrev } from '@/components/ui/paginator';
1689
+ import { Tag } from '@/components/ui/tag';
1690
+ import { ProductService, type Product } from '@/shared/services/product.service';
1691
+ import type { DataTablePaginationInstance } from 'primereact/datatable';
1692
+ import type { PaginatorRootChangeEvent } from 'primereact/paginator';
1693
+ import * as React from 'react';
1694
+
1695
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
1696
+ INSTOCK: 'success',
1697
+ LOWSTOCK: 'warn',
1698
+ OUTOFSTOCK: 'danger'
1699
+ };
1700
+
1701
+ const statusLabel: Record<string, string> = {
1702
+ INSTOCK: 'In Stock',
1703
+ LOWSTOCK: 'Low Stock',
1704
+ OUTOFSTOCK: 'Out of Stock'
1705
+ };
1706
+
1707
+ export default function PaginationDemo() {
1708
+ const [products, setProducts] = React.useState<Product[]>([]);
1709
+
1710
+ React.useEffect(() => {
1711
+ setProducts(ProductService.getProductsData());
1712
+ }, []);
1713
+
1714
+ return (
1715
+ <div className="w-full">
1716
+ <DataTable data={products} paginator defaultRows={5}>
1717
+ <DataTableTableContainer>
1718
+ <DataTableTable style={{ minWidth: '40rem' }}>
1719
+ <DataTableTHead>
1720
+ <DataTableTHeadRow>
1721
+ <DataTableTHeadCell>
1722
+ <DataTableTHeadTitle>Name</DataTableTHeadTitle>
1723
+ </DataTableTHeadCell>
1724
+ <DataTableTHeadCell>
1725
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
1726
+ </DataTableTHeadCell>
1727
+ <DataTableTHeadCell>
1728
+ <DataTableTHeadTitle>Qty</DataTableTHeadTitle>
1729
+ </DataTableTHeadCell>
1730
+ <DataTableTHeadCell>
1731
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
1732
+ </DataTableTHeadCell>
1733
+ </DataTableTHeadRow>
1734
+ </DataTableTHead>
1735
+ <DataTableTBody>
1736
+ {({ item }: { item: any }) => (
1737
+ <DataTableRow key={item.id}>
1738
+ <DataTableCell>
1739
+ <span className="font-medium">{item.name}</span>
1740
+ </DataTableCell>
1741
+ <DataTableCell>
1742
+ <Tag severity="secondary">{item.category}</Tag>
1743
+ </DataTableCell>
1744
+ <DataTableCell>{item.quantity}</DataTableCell>
1745
+ <DataTableCell>
1746
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
1747
+ </DataTableCell>
1748
+ </DataTableRow>
1749
+ )}
1750
+ </DataTableTBody>
1751
+ </DataTableTable>
1752
+ </DataTableTableContainer>
1753
+ <DataTablePagination>
1754
+ {({ page, rows, totalRecords, onPageChange }: DataTablePaginationInstance) => (
1755
+ <Paginator
1756
+ page={page + 1}
1757
+ total={totalRecords}
1758
+ itemsPerPage={rows}
1759
+ onPageChange={(e: PaginatorRootChangeEvent) => {
1760
+ if (e.originalEvent) onPageChange(e.originalEvent, e.value - 1);
1761
+ }}
1762
+ >
1763
+ <PaginatorFirst />
1764
+ <PaginatorPrev />
1765
+ <PaginatorPages />
1766
+ <PaginatorNext />
1767
+ <PaginatorLast />
1768
+ </Paginator>
1769
+ )}
1770
+ </DataTablePagination>
1771
+ </DataTable>
1772
+ </div>
1773
+ );
1774
+ }
1775
+ ```
1776
+
1777
+ ### Scroll
1778
+
1779
+ Set `scrollable` on `DataTable` to enable scrolling.
1780
+
1781
+ #### Horizontal
1782
+
1783
+ When the combined column widths exceed the container, the table scrolls horizontally.
1784
+
1785
+ ```tsx
1786
+ 'use client';
1787
+ import { CustomerService, type Customer } from '@/shared/services/customer.service';
1788
+ import { Database } from '@primeicons/react/database';
1789
+ import {
1790
+ DataTable,
1791
+ DataTableCell,
1792
+ DataTableEmptyTBody,
1793
+ DataTableRow,
1794
+ DataTableTBody,
1795
+ DataTableTHead,
1796
+ DataTableTHeadCell,
1797
+ DataTableTHeadRow,
1798
+ DataTableTHeadTitle,
1799
+ DataTableTable,
1800
+ DataTableTableContainer
1801
+ } from '@/components/ui/datatable';
1802
+ import { Tag } from '@/components/ui/tag';
1803
+ import * as React from 'react';
1804
+
1805
+ const statusSeverity: Record<string, 'success' | 'warn' | 'info' | 'danger' | 'secondary'> = {
1806
+ qualified: 'success',
1807
+ unqualified: 'danger',
1808
+ negotiation: 'warn',
1809
+ new: 'info',
1810
+ renewal: 'secondary',
1811
+ proposal: 'info'
1812
+ };
1813
+
1814
+ export default function ScrollHorizontalDemo() {
1815
+ const [customers, setCustomers] = React.useState<Customer[]>([]);
1816
+
1817
+ React.useEffect(() => {
1818
+ setCustomers(CustomerService.getData());
1819
+ }, []);
1820
+
1821
+ return (
1822
+ <div className="w-full">
1823
+ <DataTable data={customers} scrollable scrollHeight="400px">
1824
+ <DataTableTableContainer>
1825
+ <DataTableTable>
1826
+ <DataTableTHead>
1827
+ <DataTableTHeadRow>
1828
+ <DataTableTHeadCell style={{ minWidth: '6rem' }}>
1829
+ <DataTableTHeadTitle>Id</DataTableTHeadTitle>
1830
+ </DataTableTHeadCell>
1831
+ <DataTableTHeadCell style={{ minWidth: '14rem' }}>
1832
+ <DataTableTHeadTitle>Name</DataTableTHeadTitle>
1833
+ </DataTableTHeadCell>
1834
+ <DataTableTHeadCell style={{ minWidth: '14rem' }}>
1835
+ <DataTableTHeadTitle>Country</DataTableTHeadTitle>
1836
+ </DataTableTHeadCell>
1837
+ <DataTableTHeadCell style={{ minWidth: '12rem' }}>
1838
+ <DataTableTHeadTitle>Date</DataTableTHeadTitle>
1839
+ </DataTableTHeadCell>
1840
+ <DataTableTHeadCell style={{ minWidth: '10rem' }}>
1841
+ <DataTableTHeadTitle>Balance</DataTableTHeadTitle>
1842
+ </DataTableTHeadCell>
1843
+ <DataTableTHeadCell style={{ minWidth: '14rem' }}>
1844
+ <DataTableTHeadTitle>Company</DataTableTHeadTitle>
1845
+ </DataTableTHeadCell>
1846
+ <DataTableTHeadCell style={{ minWidth: '10rem' }}>
1847
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
1848
+ </DataTableTHeadCell>
1849
+ <DataTableTHeadCell style={{ minWidth: '8rem' }}>
1850
+ <DataTableTHeadTitle>Activity</DataTableTHeadTitle>
1851
+ </DataTableTHeadCell>
1852
+ <DataTableTHeadCell style={{ minWidth: '14rem' }}>
1853
+ <DataTableTHeadTitle>Representative</DataTableTHeadTitle>
1854
+ </DataTableTHeadCell>
1855
+ </DataTableTHeadRow>
1856
+ </DataTableTHead>
1857
+ <DataTableTBody>
1858
+ {({ item }: { item: any }) => (
1859
+ <DataTableRow key={item.id}>
1860
+ <DataTableCell>{item.id}</DataTableCell>
1861
+ <DataTableCell>
1862
+ <span className="font-medium">{item.name}</span>
1863
+ </DataTableCell>
1864
+ <DataTableCell>{item.country.name}</DataTableCell>
1865
+ <DataTableCell>{item.date}</DataTableCell>
1866
+ <DataTableCell>
1867
+ <span className="font-semibold">${Number(item.balance).toLocaleString()}</span>
1868
+ </DataTableCell>
1869
+ <DataTableCell>{item.company}</DataTableCell>
1870
+ <DataTableCell>
1871
+ <Tag severity={statusSeverity[item.status] ?? 'secondary'}>{item.status}</Tag>
1872
+ </DataTableCell>
1873
+ <DataTableCell>{item.activity}%</DataTableCell>
1874
+ <DataTableCell>{item.representative.name}</DataTableCell>
1875
+ </DataTableRow>
1876
+ )}
1877
+ </DataTableTBody>
1878
+ <DataTableEmptyTBody>
1879
+ <DataTableRow>
1880
+ <DataTableCell colSpan={9}>
1881
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
1882
+ <div className="relative">
1883
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
1884
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
1885
+ </div>
1886
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
1887
+ </div>
1888
+ <div>
1889
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching customers</p>
1890
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
1891
+ Hang tight, your data will appear here in a moment.
1892
+ </p>
1893
+ </div>
1894
+ </div>
1895
+ </DataTableCell>
1896
+ </DataTableRow>
1897
+ </DataTableEmptyTBody>
1898
+ </DataTableTable>
1899
+ </DataTableTableContainer>
1900
+ </DataTable>
1901
+ </div>
1902
+ );
1903
+ }
1904
+ ```
1905
+
1906
+ #### Frozen Columns
1907
+
1908
+ Pin multiple columns to either side using `frozen` and `frozenAlignment`.
1909
+
1910
+ ```tsx
1911
+ 'use client';
1912
+ import { CustomerService, type Customer } from '@/shared/services/customer.service';
1913
+ import { Database } from '@primeicons/react/database';
1914
+ import {
1915
+ DataTable,
1916
+ DataTableCell,
1917
+ DataTableEmptyTBody,
1918
+ DataTableRow,
1919
+ DataTableTBody,
1920
+ DataTableTHead,
1921
+ DataTableTHeadCell,
1922
+ DataTableTHeadRow,
1923
+ DataTableTHeadTitle,
1924
+ DataTableTable,
1925
+ DataTableTableContainer
1926
+ } from '@/components/ui/datatable';
1927
+ import { Tag } from '@/components/ui/tag';
1928
+ import * as React from 'react';
1929
+
1930
+ const statusSeverity: Record<string, 'success' | 'warn' | 'info' | 'danger' | 'secondary'> = {
1931
+ qualified: 'success',
1932
+ unqualified: 'danger',
1933
+ negotiation: 'warn',
1934
+ new: 'info',
1935
+ renewal: 'secondary',
1936
+ proposal: 'info'
1937
+ };
1938
+
1939
+ export default function ScrollFrozenColumnsMultiDemo() {
1940
+ const [customers, setCustomers] = React.useState<Customer[]>([]);
1941
+
1942
+ React.useEffect(() => {
1943
+ setCustomers(CustomerService.getData());
1944
+ }, []);
1945
+
1946
+ return (
1947
+ <div className="w-full">
1948
+ <DataTable data={customers} scrollable scrollHeight="400px">
1949
+ <DataTableTableContainer>
1950
+ <DataTableTable style={{ minWidth: '1400px' }}>
1951
+ <DataTableTHead>
1952
+ <DataTableTHeadRow>
1953
+ <DataTableTHeadCell frozen style={{ minWidth: '80px' }}>
1954
+ <DataTableTHeadTitle>Id</DataTableTHeadTitle>
1955
+ </DataTableTHeadCell>
1956
+ <DataTableTHeadCell style={{ minWidth: '200px' }}>
1957
+ <DataTableTHeadTitle>Name</DataTableTHeadTitle>
1958
+ </DataTableTHeadCell>
1959
+ <DataTableTHeadCell style={{ minWidth: '200px' }}>
1960
+ <DataTableTHeadTitle>Country</DataTableTHeadTitle>
1961
+ </DataTableTHeadCell>
1962
+ <DataTableTHeadCell style={{ minWidth: '220px' }}>
1963
+ <DataTableTHeadTitle>Company</DataTableTHeadTitle>
1964
+ </DataTableTHeadCell>
1965
+ <DataTableTHeadCell style={{ minWidth: '200px' }}>
1966
+ <DataTableTHeadTitle>Representative</DataTableTHeadTitle>
1967
+ </DataTableTHeadCell>
1968
+ <DataTableTHeadCell style={{ minWidth: '200px' }}>
1969
+ <DataTableTHeadTitle>Date</DataTableTHeadTitle>
1970
+ </DataTableTHeadCell>
1971
+ <DataTableTHeadCell style={{ minWidth: '200px' }}>
1972
+ <DataTableTHeadTitle>Activity</DataTableTHeadTitle>
1973
+ </DataTableTHeadCell>
1974
+ <DataTableTHeadCell style={{ minWidth: '200px' }}>
1975
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
1976
+ </DataTableTHeadCell>
1977
+ <DataTableTHeadCell frozen alignFrozen="right" style={{ minWidth: '180px' }}>
1978
+ <DataTableTHeadTitle>Balance</DataTableTHeadTitle>
1979
+ </DataTableTHeadCell>
1980
+ </DataTableTHeadRow>
1981
+ </DataTableTHead>
1982
+ <DataTableTBody>
1983
+ {({ item }: { item: any }) => (
1984
+ <DataTableRow key={String(item.id)}>
1985
+ <DataTableCell frozen>
1986
+ <span className="text-xs text-surface-500 dark:text-surface-400 tabular-nums">#{String(item.id)}</span>
1987
+ </DataTableCell>
1988
+ <DataTableCell>
1989
+ <span className="font-semibold">{String(item.name)}</span>
1990
+ </DataTableCell>
1991
+ <DataTableCell>
1992
+ <div className="flex items-center gap-2">
1993
+ <span className="text-lg leading-none">{CustomerService.getFlag(item.country.code)}</span>
1994
+ <span>{item.country.name}</span>
1995
+ </div>
1996
+ </DataTableCell>
1997
+ <DataTableCell>{String(item.company)}</DataTableCell>
1998
+ <DataTableCell>{String(item.representative.name)}</DataTableCell>
1999
+ <DataTableCell>{String(item.date)}</DataTableCell>
2000
+ <DataTableCell>{String(item.activity)}%</DataTableCell>
2001
+ <DataTableCell>
2002
+ <Tag severity={statusSeverity[item.status as string] ?? 'secondary'}>{String(item.status)}</Tag>
2003
+ </DataTableCell>
2004
+ <DataTableCell frozen alignFrozen="right">
2005
+ <span className="font-semibold">${Number(item.balance).toLocaleString()}</span>
2006
+ </DataTableCell>
2007
+ </DataTableRow>
2008
+ )}
2009
+ </DataTableTBody>
2010
+ <DataTableEmptyTBody>
2011
+ <DataTableRow>
2012
+ <DataTableCell colSpan={9}>
2013
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
2014
+ <div className="relative">
2015
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
2016
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
2017
+ </div>
2018
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
2019
+ </div>
2020
+ <div>
2021
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching customers</p>
2022
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
2023
+ Hang tight, your data will appear here in a moment.
2024
+ </p>
2025
+ </div>
2026
+ </div>
2027
+ </DataTableCell>
2028
+ </DataTableRow>
2029
+ </DataTableEmptyTBody>
2030
+ </DataTableTable>
2031
+ </DataTableTableContainer>
2032
+ </DataTable>
2033
+ </div>
2034
+ );
2035
+ }
2036
+ ```
2037
+
2038
+ #### Frozen Rows
2039
+
2040
+ Render a `DataTableFrozenTBody` above the regular `DataTableTBody` to keep specific rows pinned to the top.
2041
+
2042
+ ```tsx
2043
+ 'use client';
2044
+ import { CustomerService, type Customer } from '@/shared/services/customer.service';
2045
+ import { Lock } from '@primeicons/react/lock';
2046
+ import { Database } from '@primeicons/react/database';
2047
+ import { LockOpen } from '@primeicons/react/lock-open';
2048
+ import { Button } from '@/components/ui/button';
2049
+ import {
2050
+ DataTable,
2051
+ DataTableCell,
2052
+ DataTableEmptyTBody,
2053
+ DataTableFrozenTBody,
2054
+ DataTableRow,
2055
+ DataTableTBody,
2056
+ DataTableTHead,
2057
+ DataTableTHeadCell,
2058
+ DataTableTHeadRow,
2059
+ DataTableTHeadTitle,
2060
+ DataTableTable,
2061
+ DataTableTableContainer
2062
+ } from '@/components/ui/datatable';
2063
+ import { Tag } from '@/components/ui/tag';
2064
+ import * as React from 'react';
2065
+
2066
+ const statusSeverity: Record<string, 'success' | 'warn' | 'info' | 'danger' | 'secondary'> = {
2067
+ qualified: 'success',
2068
+ unqualified: 'danger',
2069
+ negotiation: 'warn',
2070
+ new: 'info',
2071
+ renewal: 'secondary',
2072
+ proposal: 'info'
2073
+ };
2074
+
2075
+ export default function ScrollFrozenRowsDemo() {
2076
+ const [customers, setCustomers] = React.useState<Customer[]>([]);
2077
+ const [lockedIds, setLockedIds] = React.useState<number[]>([]);
2078
+
2079
+ React.useEffect(() => {
2080
+ const data = CustomerService.getData();
2081
+
2082
+ setCustomers(data);
2083
+ setLockedIds([data[0].id]);
2084
+ }, []);
2085
+
2086
+ const { lockedCustomers, unlockedCustomers } = React.useMemo(() => {
2087
+ const locked: Customer[] = [];
2088
+ const unlocked: Customer[] = [];
2089
+
2090
+ for (const c of customers) {
2091
+ if (lockedIds.includes(c.id)) locked.push(c);
2092
+ else unlocked.push(c);
2093
+ }
2094
+
2095
+ return { lockedCustomers: locked, unlockedCustomers: unlocked };
2096
+ }, [customers, lockedIds]);
2097
+
2098
+ const toggleLock = React.useCallback((id: number) => {
2099
+ setLockedIds((prev) => {
2100
+ if (prev.includes(id)) return prev.filter((x) => x !== id);
2101
+ if (prev.length >= 2) return prev;
2102
+
2103
+ return [...prev, id];
2104
+ });
2105
+ }, []);
2106
+
2107
+ const canLockMore = lockedIds.length < 2;
2108
+
2109
+ // Refs so renderRow and tbodyRender keep stable identities across toggles —
2110
+ // avoids invalidating TBody's render-prop and FrozenTBody children on every
2111
+ // lock click. The refs are read at render-time, so each row still gets the
2112
+ // current canLockMore value.
2113
+ const canLockMoreRef = React.useRef(canLockMore);
2114
+
2115
+ canLockMoreRef.current = canLockMore;
2116
+
2117
+ const toggleLockRef = React.useRef(toggleLock);
2118
+
2119
+ toggleLockRef.current = toggleLock;
2120
+
2121
+ const renderRow = React.useCallback((item: Customer, { frozen = false }: { frozen?: boolean } = {}) => {
2122
+ const canLock = canLockMoreRef.current;
2123
+ const onClick = () => toggleLockRef.current(item.id);
2124
+
2125
+ return (
2126
+ <DataTableRow key={item.id}>
2127
+ <DataTableCell>
2128
+ <span className={frozen ? 'font-semibold' : 'font-medium'}>{item.name}</span>
2129
+ </DataTableCell>
2130
+ <DataTableCell>
2131
+ <div className="flex items-center gap-2">
2132
+ <span className="text-lg leading-none">{CustomerService.getFlag(item.country.code)}</span>
2133
+ <span>{item.country.name}</span>
2134
+ </div>
2135
+ </DataTableCell>
2136
+ <DataTableCell>{item.representative.name}</DataTableCell>
2137
+ <DataTableCell>
2138
+ <Tag severity={statusSeverity[item.status] ?? 'secondary'}>{item.status}</Tag>
2139
+ </DataTableCell>
2140
+ <DataTableCell style={{ width: '4rem', textAlign: 'center' }}>
2141
+ <Button
2142
+ variant="text"
2143
+ severity="secondary"
2144
+ size="small"
2145
+ iconOnly
2146
+ disabled={!frozen && !canLock}
2147
+ onClick={onClick}
2148
+ aria-label={frozen ? 'Unlock row' : 'Lock row'}
2149
+ >
2150
+ {frozen ? <LockOpen /> : <Lock />}
2151
+ </Button>
2152
+ </DataTableCell>
2153
+ </DataTableRow>
2154
+ );
2155
+ }, []);
2156
+
2157
+ const tbodyRender = React.useCallback(({ item }: { item: any }) => renderRow(item as Customer), [renderRow]);
2158
+
2159
+ return (
2160
+ <div className="w-full">
2161
+ <DataTable data={unlockedCustomers} scrollable scrollHeight="400px">
2162
+ <DataTableTableContainer>
2163
+ <DataTableTable style={{ minWidth: '50rem' }}>
2164
+ <DataTableTHead>
2165
+ <DataTableTHeadRow>
2166
+ <DataTableTHeadCell>
2167
+ <DataTableTHeadTitle>Name</DataTableTHeadTitle>
2168
+ </DataTableTHeadCell>
2169
+ <DataTableTHeadCell>
2170
+ <DataTableTHeadTitle>Country</DataTableTHeadTitle>
2171
+ </DataTableTHeadCell>
2172
+ <DataTableTHeadCell>
2173
+ <DataTableTHeadTitle>Representative</DataTableTHeadTitle>
2174
+ </DataTableTHeadCell>
2175
+ <DataTableTHeadCell>
2176
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
2177
+ </DataTableTHeadCell>
2178
+ <DataTableTHeadCell style={{ width: '4rem' }} />
2179
+ </DataTableTHeadRow>
2180
+ </DataTableTHead>
2181
+ <DataTableFrozenTBody>{lockedCustomers.map((item) => renderRow(item, { frozen: true }))}</DataTableFrozenTBody>
2182
+ <DataTableTBody>{tbodyRender}</DataTableTBody>
2183
+ <DataTableEmptyTBody>
2184
+ <DataTableRow>
2185
+ <DataTableCell colSpan={5}>
2186
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
2187
+ <div className="relative">
2188
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
2189
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
2190
+ </div>
2191
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
2192
+ </div>
2193
+ <div>
2194
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching customers</p>
2195
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
2196
+ Hang tight, your data will appear here in a moment.
2197
+ </p>
2198
+ </div>
2199
+ </div>
2200
+ </DataTableCell>
2201
+ </DataTableRow>
2202
+ </DataTableEmptyTBody>
2203
+ </DataTableTable>
2204
+ </DataTableTableContainer>
2205
+ </DataTable>
2206
+ </div>
2207
+ );
2208
+ }
2209
+ ```
2210
+
2211
+ ### Row Expansion
2212
+
2213
+ Expand rows to show additional detail content.
2214
+
2215
+ ```tsx
2216
+ 'use client';
2217
+ import { ProductService, type Product } from '@/shared/services/product.service';
2218
+ import { ChevronDown } from '@primeicons/react/chevron-down';
2219
+ import { Database } from '@primeicons/react/database';
2220
+ import { ChevronRight } from '@primeicons/react/chevron-right';
2221
+ import { Star } from '@primeicons/react/star';
2222
+ import { StarFill } from '@primeicons/react/star-fill';
2223
+ import type { DataTableExpansionEvent } from 'primereact/datatable';
2224
+ import {
2225
+ DataTable,
2226
+ DataTableCell,
2227
+ DataTableEmptyTBody,
2228
+ DataTableRow,
2229
+ DataTableRowExpansion,
2230
+ DataTableRowToggle,
2231
+ DataTableRowToggleIndicator,
2232
+ DataTableTBody,
2233
+ DataTableTHead,
2234
+ DataTableTHeadCell,
2235
+ DataTableTHeadRow,
2236
+ DataTableTHeadTitle,
2237
+ DataTableTable,
2238
+ DataTableTableContainer
2239
+ } from '@/components/ui/datatable';
2240
+ import { Rating, RatingOff, RatingOn, RatingOption } from '@/components/ui/rating';
2241
+ import { Tag } from '@/components/ui/tag';
2242
+ import * as React from 'react';
2243
+
2244
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
2245
+ INSTOCK: 'success',
2246
+ LOWSTOCK: 'warn',
2247
+ OUTOFSTOCK: 'danger'
2248
+ };
2249
+
2250
+ const statusLabel: Record<string, string> = {
2251
+ INSTOCK: 'In Stock',
2252
+ LOWSTOCK: 'Low Stock',
2253
+ OUTOFSTOCK: 'Out of Stock'
2254
+ };
2255
+
2256
+ function ProductRating({ value }: { value: number }) {
2257
+ return (
2258
+ <Rating value={value} readOnly>
2259
+ {Array.from({ length: 5 }).map((_, i) => (
2260
+ <RatingOption key={i} index={i}>
2261
+ <RatingOn>
2262
+ <StarFill />
2263
+ </RatingOn>
2264
+ <RatingOff>
2265
+ <Star />
2266
+ </RatingOff>
2267
+ </RatingOption>
2268
+ ))}
2269
+ </Rating>
2270
+ );
2271
+ }
2272
+
2273
+ function productImageUrl(image: string): string {
2274
+ return `https://primefaces.org/cdn/primevue/images/product/${image}`;
2275
+ }
2276
+
2277
+ export default function ExpansionDemo() {
2278
+ const [products, setProducts] = React.useState<Product[]>([]);
2279
+ const [expandedKeys, setExpandedKeys] = React.useState<Record<string, boolean>>({});
2280
+
2281
+ React.useEffect(() => {
2282
+ const data = ProductService.getProductsData().slice(0, 6);
2283
+
2284
+ setProducts(data);
2285
+
2286
+ // Warm the browser cache so the first row-expansion open isn't blocked
2287
+ // on a cold image fetch. The expansion panel itself only mounts when a
2288
+ // row is open, so without this prefetch the user waits for the network
2289
+ // round-trip the first time they click the toggle.
2290
+ for (const product of data) {
2291
+ const img = new window.Image();
2292
+
2293
+ img.src = productImageUrl(product.image);
2294
+ }
2295
+ }, []);
2296
+
2297
+ return (
2298
+ <div className="w-full">
2299
+ <DataTable
2300
+ data={products}
2301
+ dataKey="id"
2302
+ expandedKeys={expandedKeys}
2303
+ onExpandedChange={(e: DataTableExpansionEvent) => setExpandedKeys(e.value)}
2304
+ >
2305
+ <DataTableTableContainer>
2306
+ <DataTableTable style={{ minWidth: '50rem' }}>
2307
+ <DataTableTHead>
2308
+ <DataTableTHeadRow>
2309
+ <DataTableTHeadCell style={{ width: '3rem' }} />
2310
+ <DataTableTHeadCell>
2311
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
2312
+ </DataTableTHeadCell>
2313
+ <DataTableTHeadCell>
2314
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
2315
+ </DataTableTHeadCell>
2316
+ <DataTableTHeadCell>
2317
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
2318
+ </DataTableTHeadCell>
2319
+ <DataTableTHeadCell>
2320
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
2321
+ </DataTableTHeadCell>
2322
+ </DataTableTHeadRow>
2323
+ </DataTableTHead>
2324
+ <DataTableTBody>
2325
+ {({ item }: { item: any }) => (
2326
+ <React.Fragment key={item.id}>
2327
+ <DataTableRow>
2328
+ <DataTableCell>
2329
+ <DataTableRowToggle>
2330
+ <DataTableRowToggleIndicator match="expanded">
2331
+ <ChevronDown />
2332
+ </DataTableRowToggleIndicator>
2333
+ <DataTableRowToggleIndicator match="collapsed">
2334
+ <ChevronRight />
2335
+ </DataTableRowToggleIndicator>
2336
+ </DataTableRowToggle>
2337
+ </DataTableCell>
2338
+ <DataTableCell>
2339
+ <span className="font-medium">{item.name}</span>
2340
+ </DataTableCell>
2341
+ <DataTableCell>
2342
+ <Tag severity="secondary">{item.category}</Tag>
2343
+ </DataTableCell>
2344
+ <DataTableCell>
2345
+ <span className="font-semibold">${item.price}</span>
2346
+ </DataTableCell>
2347
+ <DataTableCell>
2348
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
2349
+ </DataTableCell>
2350
+ </DataTableRow>
2351
+ <DataTableRowExpansion>
2352
+ <DataTableCell colSpan={5}>
2353
+ <div className="flex gap-4 p-4 bg-surface-50 dark:bg-surface-900">
2354
+ <img
2355
+ src={productImageUrl(item.image)}
2356
+ alt={item.name}
2357
+ width={120}
2358
+ height={120}
2359
+ loading="eager"
2360
+ decoding="async"
2361
+ style={{ width: 120, height: 120, objectFit: 'cover', flexShrink: 0 }}
2362
+ className="rounded-md shadow-md"
2363
+ />
2364
+ <div className="flex flex-col gap-2 flex-1">
2365
+ <h4 className="m-0 text-base font-semibold">{item.name}</h4>
2366
+ <div className="text-xs text-surface-500 dark:text-surface-400">SKU: {item.code}</div>
2367
+ <div className="flex items-center gap-2">
2368
+ <ProductRating value={item.rating} />
2369
+ <span className="text-xs text-surface-500 dark:text-surface-400">({item.rating}/5)</span>
2370
+ </div>
2371
+ <div className="flex items-center gap-6 text-sm">
2372
+ <div>
2373
+ <div className="text-xs text-surface-500 dark:text-surface-400">Category</div>
2374
+ <div className="font-medium">{item.category}</div>
2375
+ </div>
2376
+ <div>
2377
+ <div className="text-xs text-surface-500 dark:text-surface-400">In stock</div>
2378
+ <div className="font-medium">{item.quantity} units</div>
2379
+ </div>
2380
+ <div>
2381
+ <div className="text-xs text-surface-500 dark:text-surface-400">Price</div>
2382
+ <div className="font-semibold">${item.price}</div>
2383
+ </div>
2384
+ </div>
2385
+ </div>
2386
+ </div>
2387
+ </DataTableCell>
2388
+ </DataTableRowExpansion>
2389
+ </React.Fragment>
2390
+ )}
2391
+ </DataTableTBody>
2392
+ <DataTableEmptyTBody>
2393
+ <DataTableRow>
2394
+ <DataTableCell colSpan={5}>
2395
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
2396
+ <div className="relative">
2397
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
2398
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
2399
+ </div>
2400
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
2401
+ </div>
2402
+ <div>
2403
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
2404
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
2405
+ Hang tight, your data will appear here in a moment.
2406
+ </p>
2407
+ </div>
2408
+ </div>
2409
+ </DataTableCell>
2410
+ </DataTableRow>
2411
+ </DataTableEmptyTBody>
2412
+ </DataTableTable>
2413
+ </DataTableTableContainer>
2414
+ </DataTable>
2415
+ </div>
2416
+ );
2417
+ }
2418
+ ```
2419
+
2420
+ ### Column Resize
2421
+
2422
+ Enable column resizing with drag handles via the `resizableColumns` prop.
2423
+
2424
+ #### Fit Mode
2425
+
2426
+ Dragging takes width from the adjacent column; total table width stays the same.
2427
+
2428
+ ```tsx
2429
+ 'use client';
2430
+ import { ProductService, type Product } from '@/shared/services/product.service';
2431
+ import { Database } from '@primeicons/react/database';
2432
+ import {
2433
+ DataTable,
2434
+ DataTableCell,
2435
+ DataTableColumnResizeIndicator,
2436
+ DataTableColumnResizer,
2437
+ DataTableEmptyTBody,
2438
+ DataTableRow,
2439
+ DataTableTBody,
2440
+ DataTableTHead,
2441
+ DataTableTHeadCell,
2442
+ DataTableTHeadRow,
2443
+ DataTableTHeadTitle,
2444
+ DataTableTable,
2445
+ DataTableTableContainer
2446
+ } from '@/components/ui/datatable';
2447
+ import { Tag } from '@/components/ui/tag';
2448
+ import Image from 'next/image';
2449
+ import * as React from 'react';
2450
+
2451
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
2452
+ INSTOCK: 'success',
2453
+ LOWSTOCK: 'warn',
2454
+ OUTOFSTOCK: 'danger'
2455
+ };
2456
+
2457
+ const statusLabel: Record<string, string> = {
2458
+ INSTOCK: 'In Stock',
2459
+ LOWSTOCK: 'Low Stock',
2460
+ OUTOFSTOCK: 'Out of Stock'
2461
+ };
2462
+
2463
+ export default function ResizeDemo() {
2464
+ const [products, setProducts] = React.useState<Product[]>([]);
2465
+
2466
+ React.useEffect(() => {
2467
+ setProducts(ProductService.getProductsData().slice(0, 6));
2468
+ }, []);
2469
+
2470
+ return (
2471
+ <div className="w-full">
2472
+ <DataTable data={products} resizableColumns columnResizeMode="fit" showGridlines>
2473
+ <DataTableTableContainer>
2474
+ <DataTableColumnResizeIndicator />
2475
+ <DataTableTable style={{ width: '100%', tableLayout: 'fixed' }}>
2476
+ <DataTableTHead>
2477
+ <DataTableTHeadRow>
2478
+ <DataTableTHeadCell>
2479
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
2480
+ <DataTableColumnResizer />
2481
+ </DataTableTHeadCell>
2482
+ <DataTableTHeadCell>
2483
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
2484
+ <DataTableColumnResizer />
2485
+ </DataTableTHeadCell>
2486
+ <DataTableTHeadCell>
2487
+ <DataTableTHeadTitle>Qty</DataTableTHeadTitle>
2488
+ <DataTableColumnResizer />
2489
+ </DataTableTHeadCell>
2490
+ <DataTableTHeadCell>
2491
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
2492
+ <DataTableColumnResizer />
2493
+ </DataTableTHeadCell>
2494
+ </DataTableTHeadRow>
2495
+ </DataTableTHead>
2496
+ <DataTableTBody>
2497
+ {({ item }: { item: any }) => (
2498
+ <DataTableRow key={item.id}>
2499
+ <DataTableCell>
2500
+ <div className="flex items-center gap-3">
2501
+ <Image
2502
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
2503
+ alt={item.name}
2504
+ width={36}
2505
+ height={36}
2506
+ className="rounded-md shadow"
2507
+ />
2508
+ <span className="font-medium truncate">{item.name}</span>
2509
+ </div>
2510
+ </DataTableCell>
2511
+ <DataTableCell>
2512
+ <Tag severity="secondary">{item.category}</Tag>
2513
+ </DataTableCell>
2514
+ <DataTableCell>{item.quantity}</DataTableCell>
2515
+ <DataTableCell>
2516
+ <span className="font-semibold">${item.price}</span>
2517
+ </DataTableCell>
2518
+ </DataTableRow>
2519
+ )}
2520
+ </DataTableTBody>
2521
+ <DataTableEmptyTBody>
2522
+ <DataTableRow>
2523
+ <DataTableCell colSpan={4}>
2524
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
2525
+ <div className="relative">
2526
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
2527
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
2528
+ </div>
2529
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
2530
+ </div>
2531
+ <div>
2532
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
2533
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
2534
+ Hang tight, your data will appear here in a moment.
2535
+ </p>
2536
+ </div>
2537
+ </div>
2538
+ </DataTableCell>
2539
+ </DataTableRow>
2540
+ </DataTableEmptyTBody>
2541
+ </DataTableTable>
2542
+ </DataTableTableContainer>
2543
+ </DataTable>
2544
+ </div>
2545
+ );
2546
+ }
2547
+ ```
2548
+
2549
+ #### Expand Mode
2550
+
2551
+ Dragging grows or shrinks the whole table; adjacent columns keep their widths.
2552
+
2553
+ ```tsx
2554
+ 'use client';
2555
+ import Image from 'next/image';
2556
+ import { Database } from '@primeicons/react/database';
2557
+ import { ProductService, type Product } from '@/shared/services/product.service';
2558
+ import {
2559
+ DataTable,
2560
+ DataTableCell,
2561
+ DataTableColumnResizeIndicator,
2562
+ DataTableColumnResizer,
2563
+ DataTableEmptyTBody,
2564
+ DataTableRow,
2565
+ DataTableTBody,
2566
+ DataTableTHead,
2567
+ DataTableTHeadCell,
2568
+ DataTableTHeadRow,
2569
+ DataTableTHeadTitle,
2570
+ DataTableTable,
2571
+ DataTableTableContainer
2572
+ } from '@/components/ui/datatable';
2573
+ import { Tag } from '@/components/ui/tag';
2574
+ import * as React from 'react';
2575
+
2576
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
2577
+ INSTOCK: 'success',
2578
+ LOWSTOCK: 'warn',
2579
+ OUTOFSTOCK: 'danger'
2580
+ };
2581
+
2582
+ const statusLabel: Record<string, string> = {
2583
+ INSTOCK: 'In Stock',
2584
+ LOWSTOCK: 'Low Stock',
2585
+ OUTOFSTOCK: 'Out of Stock'
2586
+ };
2587
+
2588
+ export default function ResizeExpandDemo() {
2589
+ const [products, setProducts] = React.useState<Product[]>([]);
2590
+
2591
+ React.useEffect(() => {
2592
+ setProducts(ProductService.getProductsData().slice(0, 6));
2593
+ }, []);
2594
+
2595
+ return (
2596
+ <div className="w-full">
2597
+ <DataTable data={products} resizableColumns columnResizeMode="expand" showGridlines scrollable scrollHeight="400px">
2598
+ <DataTableTableContainer>
2599
+ <DataTableColumnResizeIndicator />
2600
+ <DataTableTable style={{ tableLayout: 'fixed', minWidth: '50rem' }}>
2601
+ <DataTableTHead>
2602
+ <DataTableTHeadRow>
2603
+ <DataTableTHeadCell>
2604
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
2605
+ <DataTableColumnResizer />
2606
+ </DataTableTHeadCell>
2607
+ <DataTableTHeadCell>
2608
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
2609
+ <DataTableColumnResizer />
2610
+ </DataTableTHeadCell>
2611
+ <DataTableTHeadCell>
2612
+ <DataTableTHeadTitle>Qty</DataTableTHeadTitle>
2613
+ <DataTableColumnResizer />
2614
+ </DataTableTHeadCell>
2615
+ <DataTableTHeadCell>
2616
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
2617
+ <DataTableColumnResizer />
2618
+ </DataTableTHeadCell>
2619
+ <DataTableTHeadCell>
2620
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
2621
+ </DataTableTHeadCell>
2622
+ </DataTableTHeadRow>
2623
+ </DataTableTHead>
2624
+ <DataTableTBody>
2625
+ {({ item }: { item: any }) => (
2626
+ <DataTableRow key={item.id}>
2627
+ <DataTableCell>
2628
+ <div className="flex items-center gap-3">
2629
+ <Image
2630
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
2631
+ alt={item.name}
2632
+ width={36}
2633
+ height={36}
2634
+ className="rounded-md shadow"
2635
+ />
2636
+ <span className="font-medium truncate">{item.name}</span>
2637
+ </div>
2638
+ </DataTableCell>
2639
+ <DataTableCell>
2640
+ <Tag severity="secondary">{item.category}</Tag>
2641
+ </DataTableCell>
2642
+ <DataTableCell>{item.quantity}</DataTableCell>
2643
+ <DataTableCell>
2644
+ <span className="font-semibold">${item.price}</span>
2645
+ </DataTableCell>
2646
+ <DataTableCell>
2647
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
2648
+ </DataTableCell>
2649
+ </DataTableRow>
2650
+ )}
2651
+ </DataTableTBody>
2652
+ <DataTableEmptyTBody>
2653
+ <DataTableRow>
2654
+ <DataTableCell colSpan={5}>
2655
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
2656
+ <div className="relative">
2657
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
2658
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
2659
+ </div>
2660
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
2661
+ </div>
2662
+ <div>
2663
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
2664
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
2665
+ Hang tight, your data will appear here in a moment.
2666
+ </p>
2667
+ </div>
2668
+ </div>
2669
+ </DataTableCell>
2670
+ </DataTableRow>
2671
+ </DataTableEmptyTBody>
2672
+ </DataTableTable>
2673
+ </DataTableTableContainer>
2674
+ </DataTable>
2675
+ </div>
2676
+ );
2677
+ }
2678
+ ```
2679
+
2680
+ ### Column Reorder
2681
+
2682
+ Drag and drop column headers to reorder columns.
2683
+
2684
+ ```tsx
2685
+ 'use client';
2686
+ import Image from 'next/image';
2687
+ import { ProductService, type Product } from '@/shared/services/product.service';
2688
+ import type { DataTableColumnReorderEvent } from 'primereact/datatable';
2689
+ import { ArrowDown } from '@primeicons/react/arrow-down';
2690
+ import { ArrowUp } from '@primeicons/react/arrow-up';
2691
+ import { Bars } from '@primeicons/react/bars';
2692
+ import { Database } from '@primeicons/react/database';
2693
+ import {
2694
+ DataTable,
2695
+ DataTableCell,
2696
+ DataTableColumnReorder,
2697
+ DataTableColumnReorderIndicatorDown,
2698
+ DataTableColumnReorderIndicatorUp,
2699
+ DataTableColumnReorderTarget,
2700
+ DataTableEmptyTBody,
2701
+ DataTableRow,
2702
+ DataTableTBody,
2703
+ DataTableTHead,
2704
+ DataTableTHeadCell,
2705
+ DataTableTHeadRow,
2706
+ DataTableTHeadTitle,
2707
+ DataTableTable,
2708
+ DataTableTableContainer
2709
+ } from '@/components/ui/datatable';
2710
+ import { Tag } from '@/components/ui/tag';
2711
+ import * as React from 'react';
2712
+
2713
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
2714
+ INSTOCK: 'success',
2715
+ LOWSTOCK: 'warn',
2716
+ OUTOFSTOCK: 'danger'
2717
+ };
2718
+
2719
+ const statusLabel: Record<string, string> = {
2720
+ INSTOCK: 'In Stock',
2721
+ LOWSTOCK: 'Low Stock',
2722
+ OUTOFSTOCK: 'Out of Stock'
2723
+ };
2724
+
2725
+ interface Column {
2726
+ field: string;
2727
+ header: string;
2728
+ }
2729
+
2730
+ const initialColumns: Column[] = [
2731
+ { field: 'name', header: 'Product' },
2732
+ { field: 'category', header: 'Category' },
2733
+ { field: 'quantity', header: 'Quantity' },
2734
+ { field: 'price', header: 'Price' },
2735
+ { field: 'inventoryStatus', header: 'Status' }
2736
+ ];
2737
+
2738
+ function renderCell(field: string, item: Product) {
2739
+ switch (field) {
2740
+ case 'name':
2741
+ return (
2742
+ <div className="flex items-center gap-3">
2743
+ <Image
2744
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
2745
+ alt={item.name}
2746
+ width={36}
2747
+ height={36}
2748
+ className="rounded-md shadow"
2749
+ />
2750
+ <span className="font-medium">{item.name}</span>
2751
+ </div>
2752
+ );
2753
+ case 'category':
2754
+ return <Tag severity="secondary">{item.category}</Tag>;
2755
+ case 'quantity':
2756
+ return item.quantity;
2757
+ case 'price':
2758
+ return <span className="font-semibold">${item.price}</span>;
2759
+ case 'inventoryStatus':
2760
+ return <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>;
2761
+ default:
2762
+ return null;
2763
+ }
2764
+ }
2765
+
2766
+ export default function ReorderDemo() {
2767
+ const [products, setProducts] = React.useState<Product[]>([]);
2768
+ const [columns, setColumns] = React.useState<Column[]>(initialColumns);
2769
+
2770
+ React.useEffect(() => {
2771
+ setProducts(ProductService.getProductsData().slice(0, 6));
2772
+ }, []);
2773
+
2774
+ return (
2775
+ <div className="w-full">
2776
+ <DataTable
2777
+ data={products}
2778
+ reorderableColumns
2779
+ onColumnReorder={(e: DataTableColumnReorderEvent) => {
2780
+ const reordered = [...columns];
2781
+ const [moved] = reordered.splice(e.dragIndex, 1);
2782
+
2783
+ reordered.splice(e.dropIndex, 0, moved);
2784
+ setColumns(reordered);
2785
+ }}
2786
+ >
2787
+ <DataTableTableContainer>
2788
+ <DataTableTable style={{ minWidth: '50rem' }}>
2789
+ <DataTableTHead>
2790
+ <DataTableTHeadRow>
2791
+ {columns.map((col) => (
2792
+ <DataTableTHeadCell key={col.field}>
2793
+ <DataTableColumnReorderTarget className="group cursor-grab select-none">
2794
+ <DataTableColumnReorder className="text-surface-400 transition-colors group-hover:text-surface-600">
2795
+ <Bars />
2796
+ </DataTableColumnReorder>
2797
+ <DataTableTHeadTitle>{col.header}</DataTableTHeadTitle>
2798
+ </DataTableColumnReorderTarget>
2799
+ </DataTableTHeadCell>
2800
+ ))}
2801
+ </DataTableTHeadRow>
2802
+ </DataTableTHead>
2803
+ <DataTableTBody key={columns.map((c) => c.field).join(',')}>
2804
+ {({ item }: { item: any }) => (
2805
+ <DataTableRow key={item.id}>
2806
+ {columns.map((col) => (
2807
+ <DataTableCell key={col.field}>{renderCell(col.field, item)}</DataTableCell>
2808
+ ))}
2809
+ </DataTableRow>
2810
+ )}
2811
+ </DataTableTBody>
2812
+ <DataTableEmptyTBody>
2813
+ <DataTableRow>
2814
+ <DataTableCell colSpan={columns.length}>
2815
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
2816
+ <div className="relative">
2817
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
2818
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
2819
+ </div>
2820
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
2821
+ </div>
2822
+ <div>
2823
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
2824
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
2825
+ Hang tight, your data will appear here in a moment.
2826
+ </p>
2827
+ </div>
2828
+ </div>
2829
+ </DataTableCell>
2830
+ </DataTableRow>
2831
+ </DataTableEmptyTBody>
2832
+ </DataTableTable>
2833
+ </DataTableTableContainer>
2834
+ <DataTableColumnReorderIndicatorUp>
2835
+ <ArrowDown />
2836
+ </DataTableColumnReorderIndicatorUp>
2837
+ <DataTableColumnReorderIndicatorDown>
2838
+ <ArrowUp />
2839
+ </DataTableColumnReorderIndicatorDown>
2840
+ </DataTable>
2841
+ </div>
2842
+ );
2843
+ }
2844
+ ```
2845
+
2846
+ ### Row Reorder
2847
+
2848
+ Drag and drop rows to reorder data.
2849
+
2850
+ ```tsx
2851
+ 'use client';
2852
+ import Image from 'next/image';
2853
+ import { ProductService, type Product } from '@/shared/services/product.service';
2854
+ import { Bars } from '@primeicons/react/bars';
2855
+ import { Database } from '@primeicons/react/database';
2856
+ import type { DataTableRowReorderEvent } from 'primereact/datatable';
2857
+ import {
2858
+ DataTable,
2859
+ DataTableCell,
2860
+ DataTableEmptyTBody,
2861
+ DataTableRow,
2862
+ DataTableRowReorder,
2863
+ DataTableTBody,
2864
+ DataTableTHead,
2865
+ DataTableTHeadCell,
2866
+ DataTableTHeadRow,
2867
+ DataTableTHeadTitle,
2868
+ DataTableTable,
2869
+ DataTableTableContainer
2870
+ } from '@/components/ui/datatable';
2871
+ import { Tag } from '@/components/ui/tag';
2872
+ import * as React from 'react';
2873
+
2874
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
2875
+ INSTOCK: 'success',
2876
+ LOWSTOCK: 'warn',
2877
+ OUTOFSTOCK: 'danger'
2878
+ };
2879
+
2880
+ const statusLabel: Record<string, string> = {
2881
+ INSTOCK: 'In Stock',
2882
+ LOWSTOCK: 'Low Stock',
2883
+ OUTOFSTOCK: 'Out of Stock'
2884
+ };
2885
+
2886
+ export default function RowReorderDemo() {
2887
+ const [products, setProducts] = React.useState<Product[]>([]);
2888
+
2889
+ React.useEffect(() => {
2890
+ setProducts(ProductService.getProductsData().slice(0, 6));
2891
+ }, []);
2892
+
2893
+ return (
2894
+ <div className="w-full">
2895
+ <DataTable
2896
+ data={products}
2897
+ dataKey="id"
2898
+ reorderableRows
2899
+ onRowReorder={(e: DataTableRowReorderEvent) => setProducts(e.value as unknown as Product[])}
2900
+ >
2901
+ <DataTableTableContainer>
2902
+ <DataTableTable style={{ minWidth: '50rem' }}>
2903
+ <DataTableTHead>
2904
+ <DataTableTHeadRow>
2905
+ <DataTableTHeadCell style={{ width: '3rem' }} />
2906
+ <DataTableTHeadCell>
2907
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
2908
+ </DataTableTHeadCell>
2909
+ <DataTableTHeadCell>
2910
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
2911
+ </DataTableTHeadCell>
2912
+ <DataTableTHeadCell>
2913
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
2914
+ </DataTableTHeadCell>
2915
+ <DataTableTHeadCell>
2916
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
2917
+ </DataTableTHeadCell>
2918
+ </DataTableTHeadRow>
2919
+ </DataTableTHead>
2920
+ <DataTableTBody>
2921
+ {({ item, index }: { item: any; index: number }) => (
2922
+ <DataTableRow key={item.id}>
2923
+ <DataTableCell>
2924
+ <DataTableRowReorder rowIndex={index} className="cursor-grab text-surface-400 hover:text-surface-600">
2925
+ <Bars />
2926
+ </DataTableRowReorder>
2927
+ </DataTableCell>
2928
+ <DataTableCell>
2929
+ <div className="flex items-center gap-3">
2930
+ <Image
2931
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
2932
+ alt={item.name}
2933
+ width={36}
2934
+ height={36}
2935
+ className="rounded-md shadow"
2936
+ />
2937
+ <div className="flex flex-col">
2938
+ <span className="font-medium">{item.name}</span>
2939
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
2940
+ </div>
2941
+ </div>
2942
+ </DataTableCell>
2943
+ <DataTableCell>
2944
+ <Tag severity="secondary">{item.category}</Tag>
2945
+ </DataTableCell>
2946
+ <DataTableCell>
2947
+ <span className="font-semibold">${item.price}</span>
2948
+ </DataTableCell>
2949
+ <DataTableCell>
2950
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
2951
+ </DataTableCell>
2952
+ </DataTableRow>
2953
+ )}
2954
+ </DataTableTBody>
2955
+ <DataTableEmptyTBody>
2956
+ <DataTableRow>
2957
+ <DataTableCell colSpan={5}>
2958
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
2959
+ <div className="relative">
2960
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
2961
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
2962
+ </div>
2963
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
2964
+ </div>
2965
+ <div>
2966
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
2967
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
2968
+ Hang tight, your data will appear here in a moment.
2969
+ </p>
2970
+ </div>
2971
+ </div>
2972
+ </DataTableCell>
2973
+ </DataTableRow>
2974
+ </DataTableEmptyTBody>
2975
+ </DataTableTable>
2976
+ </DataTableTableContainer>
2977
+ </DataTable>
2978
+ </div>
2979
+ );
2980
+ }
2981
+ ```
2982
+
2983
+ ### Column Group
2984
+
2985
+ Multi-level headers with rowspan and colspan.
2986
+
2987
+ ```tsx
2988
+ 'use client';
2989
+ import { ArrowDown } from '@primeicons/react/arrow-down';
2990
+ import { Database } from '@primeicons/react/database';
2991
+ import { ArrowUp } from '@primeicons/react/arrow-up';
2992
+ import {
2993
+ DataTable,
2994
+ DataTableCell,
2995
+ DataTableEmptyTBody,
2996
+ DataTableRow,
2997
+ DataTableTBody,
2998
+ DataTableTFoot,
2999
+ DataTableTFootCell,
3000
+ DataTableTFootRow,
3001
+ DataTableTHead,
3002
+ DataTableTHeadCell,
3003
+ DataTableTHeadRow,
3004
+ DataTableTHeadTitle,
3005
+ DataTableTable,
3006
+ DataTableTableContainer
3007
+ } from '@/components/ui/datatable';
3008
+ import { Tag } from '@/components/ui/tag';
3009
+ import * as React from 'react';
3010
+
3011
+ interface Sale {
3012
+ id: string;
3013
+ product: string;
3014
+ lastYearSale: number;
3015
+ thisYearSale: number;
3016
+ lastYearProfit: number;
3017
+ thisYearProfit: number;
3018
+ }
3019
+
3020
+ const sales: Sale[] = [
3021
+ { id: '1', product: 'Bamboo Watch', lastYearSale: 51, thisYearSale: 40, lastYearProfit: 54406, thisYearProfit: 43342 },
3022
+ { id: '2', product: 'Black Watch', lastYearSale: 83, thisYearSale: 9, lastYearProfit: 423132, thisYearProfit: 312122 },
3023
+ { id: '3', product: 'Blue Band', lastYearSale: 38, thisYearSale: 5, lastYearProfit: 12321, thisYearProfit: 8500 },
3024
+ { id: '4', product: 'Blue T-Shirt', lastYearSale: 49, thisYearSale: 22, lastYearProfit: 745232, thisYearProfit: 650323 },
3025
+ { id: '5', product: 'Bracelet', lastYearSale: 17, thisYearSale: 79, lastYearProfit: 643242, thisYearProfit: 500332 }
3026
+ ];
3027
+
3028
+ function Trend({ current, previous }: { current: number; previous: number }) {
3029
+ const up = current >= previous;
3030
+
3031
+ return (
3032
+ <div className="inline-flex items-center gap-1">
3033
+ <span>{current}%</span>
3034
+ {up ? <ArrowUp className="w-3 h-3 text-green-500" /> : <ArrowDown className="w-3 h-3 text-red-500" />}
3035
+ </div>
3036
+ );
3037
+ }
3038
+
3039
+ export default function ColumnGroupDemo() {
3040
+ const totals = React.useMemo(() => {
3041
+ return sales.reduce(
3042
+ (acc, s) => ({
3043
+ lastYear: acc.lastYear + s.lastYearProfit,
3044
+ thisYear: acc.thisYear + s.thisYearProfit
3045
+ }),
3046
+ { lastYear: 0, thisYear: 0 }
3047
+ );
3048
+ }, []);
3049
+
3050
+ return (
3051
+ <div className="w-full">
3052
+ <DataTable data={sales} showGridlines>
3053
+ <DataTableTableContainer>
3054
+ <DataTableTable style={{ minWidth: '50rem' }}>
3055
+ <DataTableTHead>
3056
+ <DataTableTHeadRow>
3057
+ <DataTableTHeadCell rowSpan={2}>
3058
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
3059
+ </DataTableTHeadCell>
3060
+ <DataTableTHeadCell colSpan={2} style={{ textAlign: 'center' }}>
3061
+ <DataTableTHeadTitle>Sale Rate</DataTableTHeadTitle>
3062
+ </DataTableTHeadCell>
3063
+ <DataTableTHeadCell colSpan={2} style={{ textAlign: 'center' }}>
3064
+ <DataTableTHeadTitle>Profits</DataTableTHeadTitle>
3065
+ </DataTableTHeadCell>
3066
+ </DataTableTHeadRow>
3067
+ <DataTableTHeadRow>
3068
+ <DataTableTHeadCell>
3069
+ <DataTableTHeadTitle>Last Year</DataTableTHeadTitle>
3070
+ </DataTableTHeadCell>
3071
+ <DataTableTHeadCell>
3072
+ <DataTableTHeadTitle>This Year</DataTableTHeadTitle>
3073
+ </DataTableTHeadCell>
3074
+ <DataTableTHeadCell>
3075
+ <DataTableTHeadTitle>Last Year</DataTableTHeadTitle>
3076
+ </DataTableTHeadCell>
3077
+ <DataTableTHeadCell>
3078
+ <DataTableTHeadTitle>This Year</DataTableTHeadTitle>
3079
+ </DataTableTHeadCell>
3080
+ </DataTableTHeadRow>
3081
+ </DataTableTHead>
3082
+ <DataTableTBody>
3083
+ {({ item }: { item: any }) => (
3084
+ <DataTableRow key={item.id}>
3085
+ <DataTableCell>
3086
+ <span className="font-medium">{item.product}</span>
3087
+ </DataTableCell>
3088
+ <DataTableCell>{item.lastYearSale}%</DataTableCell>
3089
+ <DataTableCell>
3090
+ <Trend current={item.thisYearSale} previous={item.lastYearSale} />
3091
+ </DataTableCell>
3092
+ <DataTableCell>${item.lastYearProfit.toLocaleString()}</DataTableCell>
3093
+ <DataTableCell>
3094
+ <Tag severity={item.thisYearProfit >= item.lastYearProfit ? 'success' : 'danger'}>
3095
+ ${item.thisYearProfit.toLocaleString()}
3096
+ </Tag>
3097
+ </DataTableCell>
3098
+ </DataTableRow>
3099
+ )}
3100
+ </DataTableTBody>
3101
+ <DataTableEmptyTBody>
3102
+ <DataTableRow>
3103
+ <DataTableCell colSpan={3}>
3104
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
3105
+ <div className="relative">
3106
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
3107
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
3108
+ </div>
3109
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
3110
+ </div>
3111
+ <div>
3112
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching sales</p>
3113
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
3114
+ Hang tight, the latest figures will appear here in a moment.
3115
+ </p>
3116
+ </div>
3117
+ </div>
3118
+ </DataTableCell>
3119
+ </DataTableRow>
3120
+ </DataTableEmptyTBody>
3121
+ <DataTableTFoot>
3122
+ <DataTableTFootRow>
3123
+ <DataTableTFootCell colSpan={3} style={{ textAlign: 'right' }}>
3124
+ <span className="font-semibold">Totals</span>
3125
+ </DataTableTFootCell>
3126
+ <DataTableTFootCell>
3127
+ <span className="font-semibold">${totals.lastYear.toLocaleString()}</span>
3128
+ </DataTableTFootCell>
3129
+ <DataTableTFootCell>
3130
+ <span className="font-semibold">${totals.thisYear.toLocaleString()}</span>
3131
+ </DataTableTFootCell>
3132
+ </DataTableTFootRow>
3133
+ </DataTableTFoot>
3134
+ </DataTableTable>
3135
+ </DataTableTableContainer>
3136
+ </DataTable>
3137
+ </div>
3138
+ );
3139
+ }
3140
+ ```
3141
+
3142
+ Sort and filter work on any leaf header cell in a grouped layout.
3143
+
3144
+ ```tsx
3145
+ 'use client';
3146
+ import { ArrowDown } from '@primeicons/react/arrow-down';
3147
+ import { Database } from '@primeicons/react/database';
3148
+ import { ArrowUp } from '@primeicons/react/arrow-up';
3149
+ import { SortAlt } from '@primeicons/react/sort-alt';
3150
+ import { SortAmountDown } from '@primeicons/react/sort-amount-down';
3151
+ import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt';
3152
+ import { FilterMatchMode } from 'primereact/datatable';
3153
+ import type { DataTableFilterInstance } from 'primereact/datatable';
3154
+ import {
3155
+ DataTable,
3156
+ DataTableCell,
3157
+ DataTableEmptyTBody,
3158
+ DataTableFilter,
3159
+ DataTableRow,
3160
+ DataTableSort,
3161
+ DataTableSortIndicator,
3162
+ DataTableTBody,
3163
+ DataTableTHead,
3164
+ DataTableTHeadCell,
3165
+ DataTableTHeadRow,
3166
+ DataTableTHeadTitle,
3167
+ DataTableTable,
3168
+ DataTableTableContainer
3169
+ } from '@/components/ui/datatable';
3170
+ import { InputText } from '@/components/ui/inputtext';
3171
+ import { Tag } from '@/components/ui/tag';
3172
+ import * as React from 'react';
3173
+
3174
+ interface Sale {
3175
+ id: string;
3176
+ product: string;
3177
+ lastYearSale: number;
3178
+ thisYearSale: number;
3179
+ lastYearProfit: number;
3180
+ thisYearProfit: number;
3181
+ }
3182
+
3183
+ const sales: Sale[] = [
3184
+ { id: '1', product: 'Bamboo Watch', lastYearSale: 51, thisYearSale: 40, lastYearProfit: 54_406, thisYearProfit: 43_342 },
3185
+ { id: '2', product: 'Black Watch', lastYearSale: 83, thisYearSale: 9, lastYearProfit: 423_132, thisYearProfit: 312_122 },
3186
+ { id: '3', product: 'Blue Band', lastYearSale: 38, thisYearSale: 5, lastYearProfit: 12_321, thisYearProfit: 8_500 },
3187
+ { id: '4', product: 'Blue T-Shirt', lastYearSale: 49, thisYearSale: 22, lastYearProfit: 745_232, thisYearProfit: 650_323 },
3188
+ { id: '5', product: 'Bracelet', lastYearSale: 17, thisYearSale: 79, lastYearProfit: 643_242, thisYearProfit: 500_332 }
3189
+ ];
3190
+
3191
+ function Trend({ current, previous }: { current: number; previous: number }) {
3192
+ const up = current >= previous;
3193
+
3194
+ return (
3195
+ <div className="inline-flex items-center gap-1">
3196
+ <span>{current}%</span>
3197
+ {up ? <ArrowUp className="w-3 h-3 text-green-500" /> : <ArrowDown className="w-3 h-3 text-red-500" />}
3198
+ </div>
3199
+ );
3200
+ }
3201
+
3202
+ function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) {
3203
+ return (
3204
+ <DataTableSort field={field} className="inline-flex items-center gap-1">
3205
+ <DataTableTHeadTitle>{children}</DataTableTHeadTitle>
3206
+ <DataTableSortIndicator match="unsorted">
3207
+ <SortAlt />
3208
+ </DataTableSortIndicator>
3209
+ <DataTableSortIndicator match="asc">
3210
+ <SortAmountUpAlt />
3211
+ </DataTableSortIndicator>
3212
+ <DataTableSortIndicator match="desc">
3213
+ <SortAmountDown />
3214
+ </DataTableSortIndicator>
3215
+ </DataTableSort>
3216
+ );
3217
+ }
3218
+
3219
+ export default function ColumnGroupFilterSortDemo() {
3220
+ const [filters, setFilters] = React.useState<Record<string, { value: unknown; matchMode: FilterMatchMode }>>({
3221
+ product: { value: null, matchMode: FilterMatchMode.Contains },
3222
+ thisYearSale: { value: null, matchMode: FilterMatchMode.Gte }
3223
+ });
3224
+
3225
+ return (
3226
+ <div className="w-full">
3227
+ <DataTable
3228
+ data={sales}
3229
+ removableSort
3230
+ filters={filters}
3231
+ onFilter={(e: { filters: Record<string, unknown> }) => setFilters(e.filters as typeof filters)}
3232
+ showGridlines
3233
+ >
3234
+ <DataTableTableContainer>
3235
+ <DataTableTable style={{ minWidth: '50rem' }}>
3236
+ <DataTableTHead>
3237
+ <DataTableTHeadRow>
3238
+ <DataTableTHeadCell rowSpan={2}>
3239
+ <SortableHeader field="product">Product</SortableHeader>
3240
+ </DataTableTHeadCell>
3241
+ <DataTableTHeadCell colSpan={2} style={{ textAlign: 'center' }}>
3242
+ <DataTableTHeadTitle>Sale Rate</DataTableTHeadTitle>
3243
+ </DataTableTHeadCell>
3244
+ <DataTableTHeadCell colSpan={2} style={{ textAlign: 'center' }}>
3245
+ <DataTableTHeadTitle>Profits</DataTableTHeadTitle>
3246
+ </DataTableTHeadCell>
3247
+ </DataTableTHeadRow>
3248
+ <DataTableTHeadRow>
3249
+ <DataTableTHeadCell>
3250
+ <SortableHeader field="lastYearSale">Last Year</SortableHeader>
3251
+ </DataTableTHeadCell>
3252
+ <DataTableTHeadCell>
3253
+ <SortableHeader field="thisYearSale">This Year</SortableHeader>
3254
+ </DataTableTHeadCell>
3255
+ <DataTableTHeadCell>
3256
+ <SortableHeader field="lastYearProfit">Last Year</SortableHeader>
3257
+ </DataTableTHeadCell>
3258
+ <DataTableTHeadCell>
3259
+ <SortableHeader field="thisYearProfit">This Year</SortableHeader>
3260
+ </DataTableTHeadCell>
3261
+ </DataTableTHeadRow>
3262
+ <DataTableTHeadRow>
3263
+ <DataTableTHeadCell>
3264
+ <DataTableFilter field="product" display="row" dataType="text">
3265
+ {({ value, onChange }: DataTableFilterInstance) => (
3266
+ <InputText
3267
+ value={(value as string) ?? ''}
3268
+ onChange={(e: any) => onChange(e, e.target.value)}
3269
+ placeholder="Search"
3270
+ size="small"
3271
+ fluid
3272
+ />
3273
+ )}
3274
+ </DataTableFilter>
3275
+ </DataTableTHeadCell>
3276
+ <DataTableTHeadCell />
3277
+ <DataTableTHeadCell>
3278
+ <DataTableFilter field="thisYearSale" display="row" dataType="numeric">
3279
+ {({ value, onChange }: DataTableFilterInstance) => (
3280
+ <InputText
3281
+ value={(value as string) ?? ''}
3282
+ onChange={(e: any) => onChange(e, e.target.value ? Number(e.target.value) : null, 'gte')}
3283
+ placeholder="≥"
3284
+ size="small"
3285
+ fluid
3286
+ />
3287
+ )}
3288
+ </DataTableFilter>
3289
+ </DataTableTHeadCell>
3290
+ <DataTableTHeadCell />
3291
+ <DataTableTHeadCell />
3292
+ </DataTableTHeadRow>
3293
+ </DataTableTHead>
3294
+ <DataTableTBody>
3295
+ {({ item }: { item: any }) => (
3296
+ <DataTableRow key={item.id}>
3297
+ <DataTableCell>
3298
+ <span className="font-medium">{item.product}</span>
3299
+ </DataTableCell>
3300
+ <DataTableCell>{item.lastYearSale}%</DataTableCell>
3301
+ <DataTableCell>
3302
+ <Trend current={item.thisYearSale} previous={item.lastYearSale} />
3303
+ </DataTableCell>
3304
+ <DataTableCell>${item.lastYearProfit.toLocaleString()}</DataTableCell>
3305
+ <DataTableCell>
3306
+ <Tag severity={item.thisYearProfit >= item.lastYearProfit ? 'success' : 'danger'}>
3307
+ ${item.thisYearProfit.toLocaleString()}
3308
+ </Tag>
3309
+ </DataTableCell>
3310
+ </DataTableRow>
3311
+ )}
3312
+ </DataTableTBody>
3313
+ <DataTableEmptyTBody>
3314
+ <DataTableRow>
3315
+ <DataTableCell colSpan={5}>
3316
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
3317
+ <div className="relative">
3318
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
3319
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
3320
+ </div>
3321
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
3322
+ </div>
3323
+ <div>
3324
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching sales</p>
3325
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
3326
+ Hang tight, the latest figures will appear here in a moment.
3327
+ </p>
3328
+ </div>
3329
+ </div>
3330
+ </DataTableCell>
3331
+ </DataTableRow>
3332
+ </DataTableEmptyTBody>
3333
+ </DataTableTable>
3334
+ </DataTableTableContainer>
3335
+ </DataTable>
3336
+ </div>
3337
+ );
3338
+ }
3339
+ ```
3340
+
3341
+ ### Filter
3342
+
3343
+ Filtering is driven by a `filters` object passed to `DataTable`. Global search is opt-in via `globalFilter` + `globalFilterFields`.
3344
+
3345
+ ```tsx
3346
+ 'use client';
3347
+ import {
3348
+ DataTable,
3349
+ DataTableCell,
3350
+ DataTableHeader,
3351
+ DataTableRow,
3352
+ DataTableTBody,
3353
+ DataTableTHead,
3354
+ DataTableTHeadCell,
3355
+ DataTableTHeadRow,
3356
+ DataTableTable,
3357
+ DataTableTableContainer
3358
+ } from '@/components/ui/datatable';
3359
+ import { IconField } from '@/components/ui/iconfield';
3360
+ import { InputText } from '@/components/ui/inputtext';
3361
+ import { Tag } from '@/components/ui/tag';
3362
+ import { ProductService, type Product } from '@/shared/services/product.service';
3363
+ import { Search } from '@primeicons/react/search';
3364
+ import * as React from 'react';
3365
+
3366
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
3367
+ INSTOCK: 'success',
3368
+ LOWSTOCK: 'warn',
3369
+ OUTOFSTOCK: 'danger'
3370
+ };
3371
+
3372
+ const statusLabel: Record<string, string> = {
3373
+ INSTOCK: 'In Stock',
3374
+ LOWSTOCK: 'Low Stock',
3375
+ OUTOFSTOCK: 'Out of Stock'
3376
+ };
3377
+
3378
+ export default function FilterDemo() {
3379
+ const [products, setProducts] = React.useState<Product[]>([]);
3380
+ const [globalFilter, setGlobalFilter] = React.useState('');
3381
+
3382
+ React.useEffect(() => {
3383
+ setProducts(ProductService.getProductsData());
3384
+ }, []);
3385
+
3386
+ return (
3387
+ <div className="w-full">
3388
+ <DataTable
3389
+ data={products}
3390
+ globalFilter={globalFilter}
3391
+ globalFilterFields={['name', 'category', 'inventoryStatus']}
3392
+ paginator
3393
+ defaultRows={5}
3394
+ >
3395
+ <DataTableHeader>
3396
+ <div className="flex justify-end">
3397
+ <IconField>
3398
+ <Search />
3399
+ <InputText
3400
+ placeholder="Search products..."
3401
+ value={globalFilter}
3402
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setGlobalFilter(e.target.value)}
3403
+ />
3404
+ </IconField>
3405
+ </div>
3406
+ </DataTableHeader>
3407
+ <DataTableTableContainer>
3408
+ <DataTableTable style={{ minWidth: '40rem' }}>
3409
+ <DataTableTHead>
3410
+ <DataTableTHeadRow>
3411
+ <DataTableTHeadCell>Name</DataTableTHeadCell>
3412
+ <DataTableTHeadCell>Category</DataTableTHeadCell>
3413
+ <DataTableTHeadCell>Price</DataTableTHeadCell>
3414
+ <DataTableTHeadCell>Status</DataTableTHeadCell>
3415
+ </DataTableTHeadRow>
3416
+ </DataTableTHead>
3417
+ <DataTableTBody>
3418
+ {({ item }: { item: any }) => (
3419
+ <DataTableRow key={item.id}>
3420
+ <DataTableCell>
3421
+ <span className="font-medium">{item.name}</span>
3422
+ </DataTableCell>
3423
+ <DataTableCell>
3424
+ <Tag severity="secondary">{item.category}</Tag>
3425
+ </DataTableCell>
3426
+ <DataTableCell>
3427
+ <span className="font-semibold">${item.price}</span>
3428
+ </DataTableCell>
3429
+ <DataTableCell>
3430
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
3431
+ </DataTableCell>
3432
+ </DataTableRow>
3433
+ )}
3434
+ </DataTableTBody>
3435
+ </DataTableTable>
3436
+ </DataTableTableContainer>
3437
+ </DataTable>
3438
+ </div>
3439
+ );
3440
+ }
3441
+ ```
3442
+
3443
+ ### Header & Footer
3444
+
3445
+ Use `DataTableHeader` and `DataTableFooter` to add summary information above and below the table.
3446
+
3447
+ ```tsx
3448
+ 'use client';
3449
+ import {
3450
+ DataTable,
3451
+ DataTableCell,
3452
+ DataTableFooter,
3453
+ DataTableHeader,
3454
+ DataTableRow,
3455
+ DataTableTBody,
3456
+ DataTableTHead,
3457
+ DataTableTHeadCell,
3458
+ DataTableTHeadRow,
3459
+ DataTableTable,
3460
+ DataTableTableContainer
3461
+ } from '@/components/ui/datatable';
3462
+ import { Tag } from '@/components/ui/tag';
3463
+ import { ProductService, type Product } from '@/shared/services/product.service';
3464
+ import Image from 'next/image';
3465
+ import * as React from 'react';
3466
+
3467
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
3468
+ INSTOCK: 'success',
3469
+ LOWSTOCK: 'warn',
3470
+ OUTOFSTOCK: 'danger'
3471
+ };
3472
+
3473
+ const statusLabel: Record<string, string> = {
3474
+ INSTOCK: 'In Stock',
3475
+ LOWSTOCK: 'Low Stock',
3476
+ OUTOFSTOCK: 'Out of Stock'
3477
+ };
3478
+
3479
+ export default function TemplateDemo() {
3480
+ const [products, setProducts] = React.useState<Product[]>([]);
3481
+
3482
+ React.useEffect(() => {
3483
+ setProducts(ProductService.getProductsData().slice(0, 6));
3484
+ }, []);
3485
+
3486
+ return (
3487
+ <div className="w-full">
3488
+ <DataTable data={products}>
3489
+ <DataTableHeader>
3490
+ <div className="flex items-center justify-between">
3491
+ <span className="text-base font-semibold">Products</span>
3492
+ <span className="text-xs text-surface-500 dark:text-surface-400">{products.length} items</span>
3493
+ </div>
3494
+ </DataTableHeader>
3495
+ <DataTableTableContainer>
3496
+ <DataTableTable style={{ minWidth: '40rem' }}>
3497
+ <DataTableTHead>
3498
+ <DataTableTHeadRow>
3499
+ <DataTableTHeadCell>Product</DataTableTHeadCell>
3500
+ <DataTableTHeadCell>Category</DataTableTHeadCell>
3501
+ <DataTableTHeadCell>Price</DataTableTHeadCell>
3502
+ <DataTableTHeadCell>Status</DataTableTHeadCell>
3503
+ </DataTableTHeadRow>
3504
+ </DataTableTHead>
3505
+ <DataTableTBody>
3506
+ {({ item }: { item: any }) => (
3507
+ <DataTableRow key={item.id}>
3508
+ <DataTableCell>
3509
+ <div className="flex items-center gap-3">
3510
+ <Image
3511
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
3512
+ alt={item.name}
3513
+ width={40}
3514
+ height={40}
3515
+ className="rounded-md shadow"
3516
+ />
3517
+ <span className="font-medium">{item.name}</span>
3518
+ </div>
3519
+ </DataTableCell>
3520
+ <DataTableCell>
3521
+ <Tag severity="secondary">{item.category}</Tag>
3522
+ </DataTableCell>
3523
+ <DataTableCell>
3524
+ <span className="font-semibold">${item.price}</span>
3525
+ </DataTableCell>
3526
+ <DataTableCell>
3527
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
3528
+ </DataTableCell>
3529
+ </DataTableRow>
3530
+ )}
3531
+ </DataTableTBody>
3532
+ </DataTableTable>
3533
+ </DataTableTableContainer>
3534
+ <DataTableFooter>
3535
+ <div className="text-xs text-surface-500 dark:text-surface-400">
3536
+ Total products: <span className="font-semibold text-surface-900 dark:text-surface-0">{products.length}</span>
3537
+ </div>
3538
+ </DataTableFooter>
3539
+ </DataTable>
3540
+ </div>
3541
+ );
3542
+ }
3543
+ ```
3544
+
3545
+ ### Export
3546
+
3547
+ Export table data to CSV with customizable fields and headers.
3548
+
3549
+ ```tsx
3550
+ 'use client';
3551
+ import Image from 'next/image';
3552
+ import { ProductService, type Product } from '@/shared/services/product.service';
3553
+ import { FileExport } from '@primeicons/react/file-export';
3554
+ import { Database } from '@primeicons/react/database';
3555
+ import { Button } from '@/components/ui/button';
3556
+ import {
3557
+ DataTable,
3558
+ DataTableCell,
3559
+ DataTableEmptyTBody,
3560
+ DataTableExport,
3561
+ DataTableRow,
3562
+ DataTableTBody,
3563
+ DataTableTHead,
3564
+ DataTableTHeadCell,
3565
+ DataTableTHeadRow,
3566
+ DataTableTHeadTitle,
3567
+ DataTableTable,
3568
+ DataTableTableContainer
3569
+ } from '@/components/ui/datatable';
3570
+ import { Tag } from '@/components/ui/tag';
3571
+ import * as React from 'react';
3572
+
3573
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
3574
+ INSTOCK: 'success',
3575
+ LOWSTOCK: 'warn',
3576
+ OUTOFSTOCK: 'danger'
3577
+ };
3578
+
3579
+ const statusLabel: Record<string, string> = {
3580
+ INSTOCK: 'In Stock',
3581
+ LOWSTOCK: 'Low Stock',
3582
+ OUTOFSTOCK: 'Out of Stock'
3583
+ };
3584
+
3585
+ export default function ExportDemo() {
3586
+ const [products, setProducts] = React.useState<Product[]>([]);
3587
+
3588
+ React.useEffect(() => {
3589
+ setProducts(ProductService.getProductsData().slice(0, 8));
3590
+ }, []);
3591
+
3592
+ return (
3593
+ <div className="w-full">
3594
+ <div className="flex items-center justify-between gap-3 mb-3">
3595
+ <span className="text-sm text-surface-500 dark:text-surface-400">Export visible rows to CSV with custom column headers.</span>
3596
+ <DataTableExport
3597
+ as={Button}
3598
+ size="small"
3599
+ fileName="products"
3600
+ headers={{ code: 'Code', name: 'Name', category: 'Category', price: 'Price', quantity: 'Quantity', inventoryStatus: 'Status' }}
3601
+ >
3602
+ <FileExport />
3603
+ Export CSV
3604
+ </DataTableExport>
3605
+ </div>
3606
+ <DataTable data={products}>
3607
+ <DataTableTableContainer>
3608
+ <DataTableTable style={{ minWidth: '50rem' }}>
3609
+ <DataTableTHead>
3610
+ <DataTableTHeadRow>
3611
+ <DataTableTHeadCell>
3612
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
3613
+ </DataTableTHeadCell>
3614
+ <DataTableTHeadCell>
3615
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
3616
+ </DataTableTHeadCell>
3617
+ <DataTableTHeadCell>
3618
+ <DataTableTHeadTitle>Qty</DataTableTHeadTitle>
3619
+ </DataTableTHeadCell>
3620
+ <DataTableTHeadCell>
3621
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
3622
+ </DataTableTHeadCell>
3623
+ <DataTableTHeadCell>
3624
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
3625
+ </DataTableTHeadCell>
3626
+ </DataTableTHeadRow>
3627
+ </DataTableTHead>
3628
+ <DataTableTBody>
3629
+ {({ item }: { item: any }) => (
3630
+ <DataTableRow key={item.id}>
3631
+ <DataTableCell>
3632
+ <div className="flex items-center gap-3">
3633
+ <Image
3634
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
3635
+ alt={item.name}
3636
+ width={36}
3637
+ height={36}
3638
+ className="rounded-md shadow"
3639
+ />
3640
+ <div className="flex flex-col">
3641
+ <span className="font-medium">{item.name}</span>
3642
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
3643
+ </div>
3644
+ </div>
3645
+ </DataTableCell>
3646
+ <DataTableCell>
3647
+ <Tag severity="secondary">{item.category}</Tag>
3648
+ </DataTableCell>
3649
+ <DataTableCell>{item.quantity}</DataTableCell>
3650
+ <DataTableCell>
3651
+ <span className="font-semibold">${item.price}</span>
3652
+ </DataTableCell>
3653
+ <DataTableCell>
3654
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
3655
+ </DataTableCell>
3656
+ </DataTableRow>
3657
+ )}
3658
+ </DataTableTBody>
3659
+ <DataTableEmptyTBody>
3660
+ <DataTableRow>
3661
+ <DataTableCell colSpan={5}>
3662
+ <div className="flex flex-col items-center justify-center gap-3 py-24 text-center">
3663
+ <div className="relative">
3664
+ <div className="w-16 h-16 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
3665
+ <Database className="w-8 h-8 text-surface-400 dark:text-surface-500" />
3666
+ </div>
3667
+ <span className="absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-full border-2 border-surface-0 dark:border-surface-900 bg-primary animate-pulse" />
3668
+ </div>
3669
+ <div>
3670
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">Fetching products</p>
3671
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
3672
+ Hang tight, your data will appear here in a moment.
3673
+ </p>
3674
+ </div>
3675
+ </div>
3676
+ </DataTableCell>
3677
+ </DataTableRow>
3678
+ </DataTableEmptyTBody>
3679
+ </DataTableTable>
3680
+ </DataTableTableContainer>
3681
+ </DataTable>
3682
+ </div>
3683
+ );
3684
+ }
3685
+ ```
3686
+
3687
+ ### Loading
3688
+
3689
+ #### Overlay
3690
+
3691
+ Set the `loading` prop on `DataTable` and provide a spinner inside `DataTableLoading`. An absolutely positioned overlay dims the table.
3692
+
3693
+ ```tsx
3694
+ 'use client';
3695
+ import { ProductService, type Product } from '@/shared/services/product.service';
3696
+ import { Refresh } from '@primeicons/react/refresh';
3697
+ import { Spinner } from '@primeicons/react/spinner';
3698
+ import { Button } from '@/components/ui/button';
3699
+ import {
3700
+ DataTable,
3701
+ DataTableCell,
3702
+ DataTableLoading,
3703
+ DataTableRow,
3704
+ DataTableTBody,
3705
+ DataTableTHead,
3706
+ DataTableTHeadCell,
3707
+ DataTableTHeadRow,
3708
+ DataTableTHeadTitle,
3709
+ DataTableTable,
3710
+ DataTableTableContainer
3711
+ } from '@/components/ui/datatable';
3712
+ import { Tag } from '@/components/ui/tag';
3713
+ import Image from 'next/image';
3714
+ import * as React from 'react';
3715
+
3716
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
3717
+ INSTOCK: 'success',
3718
+ LOWSTOCK: 'warn',
3719
+ OUTOFSTOCK: 'danger'
3720
+ };
3721
+
3722
+ const statusLabel: Record<string, string> = {
3723
+ INSTOCK: 'In Stock',
3724
+ LOWSTOCK: 'Low Stock',
3725
+ OUTOFSTOCK: 'Out of Stock'
3726
+ };
3727
+
3728
+ export default function LoadingOverlayDemo() {
3729
+ const [products, setProducts] = React.useState<Product[]>([]);
3730
+ const [isLoading, setIsLoading] = React.useState(false);
3731
+
3732
+ const refresh = React.useCallback(() => {
3733
+ setIsLoading(true);
3734
+ setTimeout(() => {
3735
+ setProducts(ProductService.getProductsData().slice(0, 6));
3736
+ setIsLoading(false);
3737
+ }, 1500);
3738
+ }, []);
3739
+
3740
+ React.useEffect(() => {
3741
+ refresh();
3742
+ }, [refresh]);
3743
+
3744
+ return (
3745
+ <div className="w-full">
3746
+ <div className="mb-3 flex items-center justify-between gap-3">
3747
+ <span className="text-sm text-surface-500 dark:text-surface-400">Click refresh to simulate a network fetch.</span>
3748
+ <Button size="small" onClick={refresh} disabled={isLoading}>
3749
+ <Refresh />
3750
+ Refresh
3751
+ </Button>
3752
+ </div>
3753
+ <div className="relative">
3754
+ <DataTable data={products} loading={isLoading}>
3755
+ <DataTableTableContainer>
3756
+ <DataTableTable style={{ minWidth: '50rem' }}>
3757
+ <DataTableTHead>
3758
+ <DataTableTHeadRow>
3759
+ <DataTableTHeadCell>
3760
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
3761
+ </DataTableTHeadCell>
3762
+ <DataTableTHeadCell>
3763
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
3764
+ </DataTableTHeadCell>
3765
+ <DataTableTHeadCell>
3766
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
3767
+ </DataTableTHeadCell>
3768
+ <DataTableTHeadCell>
3769
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
3770
+ </DataTableTHeadCell>
3771
+ </DataTableTHeadRow>
3772
+ </DataTableTHead>
3773
+ <DataTableTBody>
3774
+ {({ item }: { item: any }) => (
3775
+ <DataTableRow key={item.id}>
3776
+ <DataTableCell>
3777
+ <div className="flex items-center gap-3">
3778
+ <Image
3779
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
3780
+ alt={item.name}
3781
+ width={40}
3782
+ height={40}
3783
+ className="rounded-md shadow"
3784
+ />
3785
+ <div className="flex flex-col">
3786
+ <span className="font-medium">{item.name}</span>
3787
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
3788
+ </div>
3789
+ </div>
3790
+ </DataTableCell>
3791
+ <DataTableCell>
3792
+ <Tag severity="secondary">{item.category}</Tag>
3793
+ </DataTableCell>
3794
+ <DataTableCell>
3795
+ <span className="font-semibold">${item.price}</span>
3796
+ </DataTableCell>
3797
+ <DataTableCell>
3798
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
3799
+ </DataTableCell>
3800
+ </DataTableRow>
3801
+ )}
3802
+ </DataTableTBody>
3803
+ </DataTableTable>
3804
+ </DataTableTableContainer>
3805
+ <DataTableLoading>
3806
+ <div className="flex flex-col items-center gap-2">
3807
+ <Spinner className="w-10! h-10! animate-spin text-primary" />
3808
+ <span className="text-sm text-surface-600 dark:text-surface-300">Loading products…</span>
3809
+ </div>
3810
+ </DataTableLoading>
3811
+ </DataTable>
3812
+ </div>
3813
+ </div>
3814
+ );
3815
+ }
3816
+ ```
3817
+
3818
+ #### Skeleton
3819
+
3820
+ Render placeholder rows filled with `Skeleton` elements while the request is in flight.
3821
+
3822
+ ```tsx
3823
+ 'use client';
3824
+ import { ProductService, type Product } from '@/shared/services/product.service';
3825
+ import { Refresh } from '@primeicons/react/refresh';
3826
+ import { Button } from '@/components/ui/button';
3827
+ import {
3828
+ DataTable,
3829
+ DataTableCell,
3830
+ DataTableRow,
3831
+ DataTableTBody,
3832
+ DataTableTHead,
3833
+ DataTableTHeadCell,
3834
+ DataTableTHeadRow,
3835
+ DataTableTHeadTitle,
3836
+ DataTableTable,
3837
+ DataTableTableContainer
3838
+ } from '@/components/ui/datatable';
3839
+ import { Skeleton } from '@/components/ui/skeleton';
3840
+ import { Tag } from '@/components/ui/tag';
3841
+ import Image from 'next/image';
3842
+ import * as React from 'react';
3843
+
3844
+ const statusSeverity: Record<string, 'success' | 'warn' | 'danger'> = {
3845
+ INSTOCK: 'success',
3846
+ LOWSTOCK: 'warn',
3847
+ OUTOFSTOCK: 'danger'
3848
+ };
3849
+
3850
+ const statusLabel: Record<string, string> = {
3851
+ INSTOCK: 'In Stock',
3852
+ LOWSTOCK: 'Low Stock',
3853
+ OUTOFSTOCK: 'Out of Stock'
3854
+ };
3855
+
3856
+ const placeholders = Array.from({ length: 6 }, (_, i) => ({ id: `placeholder-${i}` }));
3857
+
3858
+ export default function LoadingSkeletonDemo() {
3859
+ const [products, setProducts] = React.useState<Product[]>([]);
3860
+ const [isLoading, setIsLoading] = React.useState(false);
3861
+
3862
+ const refresh = React.useCallback(() => {
3863
+ setIsLoading(true);
3864
+ setProducts([]);
3865
+ setTimeout(() => {
3866
+ setProducts(ProductService.getProductsData().slice(0, 6));
3867
+ setIsLoading(false);
3868
+ }, 1500);
3869
+ }, []);
3870
+
3871
+ React.useEffect(() => {
3872
+ refresh();
3873
+ }, [refresh]);
3874
+
3875
+ const rows = isLoading ? placeholders : products;
3876
+
3877
+ return (
3878
+ <div className="w-full">
3879
+ <div className="mb-3 flex items-center justify-between gap-3">
3880
+ <span className="text-sm text-surface-500 dark:text-surface-400">Click refresh to simulate a network fetch.</span>
3881
+ <Button size="small" onClick={refresh} disabled={isLoading}>
3882
+ <Refresh />
3883
+ Refresh
3884
+ </Button>
3885
+ </div>
3886
+ <DataTable data={rows}>
3887
+ <DataTableTableContainer>
3888
+ <DataTableTable style={{ minWidth: '50rem' }}>
3889
+ <DataTableTHead>
3890
+ <DataTableTHeadRow>
3891
+ <DataTableTHeadCell style={{ width: '40%' }}>
3892
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
3893
+ </DataTableTHeadCell>
3894
+ <DataTableTHeadCell style={{ width: '20%' }}>
3895
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
3896
+ </DataTableTHeadCell>
3897
+ <DataTableTHeadCell style={{ width: '15%' }}>
3898
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
3899
+ </DataTableTHeadCell>
3900
+ <DataTableTHeadCell style={{ width: '25%' }}>
3901
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
3902
+ </DataTableTHeadCell>
3903
+ </DataTableTHeadRow>
3904
+ </DataTableTHead>
3905
+ <DataTableTBody>
3906
+ {({ item }: { item: any }) =>
3907
+ isLoading ? (
3908
+ <DataTableRow key={item.id}>
3909
+ <DataTableCell>
3910
+ <div className="flex items-center gap-3">
3911
+ <Skeleton width="40px" height="40px" borderRadius="6px" />
3912
+ <div className="flex flex-col gap-1">
3913
+ <Skeleton width="8rem" height="0.6rem" borderRadius="4px" />
3914
+ <Skeleton width="5rem" height="0.55rem" borderRadius="4px" />
3915
+ </div>
3916
+ </div>
3917
+ </DataTableCell>
3918
+ <DataTableCell>
3919
+ <Skeleton width="5rem" height="1rem" borderRadius="16px" />
3920
+ </DataTableCell>
3921
+ <DataTableCell>
3922
+ <Skeleton width="3rem" height="0.6rem" borderRadius="4px" />
3923
+ </DataTableCell>
3924
+ <DataTableCell>
3925
+ <Skeleton width="6rem" height="1rem" borderRadius="16px" />
3926
+ </DataTableCell>
3927
+ </DataTableRow>
3928
+ ) : (
3929
+ <DataTableRow key={item.id}>
3930
+ <DataTableCell>
3931
+ <div className="flex items-center gap-3">
3932
+ <Image
3933
+ src={`https://primefaces.org/cdn/primevue/images/product/${item.image}`}
3934
+ alt={item.name}
3935
+ width={40}
3936
+ height={40}
3937
+ className="rounded-md shadow"
3938
+ />
3939
+ <div className="flex flex-col">
3940
+ <span className="font-medium">{item.name}</span>
3941
+ <span className="text-xs text-surface-500 dark:text-surface-400">{item.code}</span>
3942
+ </div>
3943
+ </div>
3944
+ </DataTableCell>
3945
+ <DataTableCell>
3946
+ <Tag severity="secondary">{item.category}</Tag>
3947
+ </DataTableCell>
3948
+ <DataTableCell>
3949
+ <span className="font-semibold">${item.price}</span>
3950
+ </DataTableCell>
3951
+ <DataTableCell>
3952
+ <Tag severity={statusSeverity[item.inventoryStatus]}>{statusLabel[item.inventoryStatus]}</Tag>
3953
+ </DataTableCell>
3954
+ </DataTableRow>
3955
+ )
3956
+ }
3957
+ </DataTableTBody>
3958
+ </DataTableTable>
3959
+ </DataTableTableContainer>
3960
+ </DataTable>
3961
+ </div>
3962
+ );
3963
+ }
3964
+ ```
3965
+
3966
+ ### Empty State
3967
+
3968
+ Custom empty state when no data is available, using `EmptyTBody`.
3969
+
3970
+ ```tsx
3971
+ 'use client';
3972
+ import { Inbox } from '@primeicons/react/inbox';
3973
+ import { Plus } from '@primeicons/react/plus';
3974
+ import { Button } from '@/components/ui/button';
3975
+ import {
3976
+ DataTable,
3977
+ DataTableCell,
3978
+ DataTableEmptyTBody,
3979
+ DataTableRow,
3980
+ DataTableTBody,
3981
+ DataTableTHead,
3982
+ DataTableTHeadCell,
3983
+ DataTableTHeadRow,
3984
+ DataTableTHeadTitle,
3985
+ DataTableTable,
3986
+ DataTableTableContainer
3987
+ } from '@/components/ui/datatable';
3988
+
3989
+ export default function EmptyDemo() {
3990
+ return (
3991
+ <div className="w-full">
3992
+ <DataTable data={[]}>
3993
+ <DataTableTableContainer>
3994
+ <DataTableTable style={{ minWidth: '50rem' }}>
3995
+ <DataTableTHead>
3996
+ <DataTableTHeadRow>
3997
+ <DataTableTHeadCell>
3998
+ <DataTableTHeadTitle>Product</DataTableTHeadTitle>
3999
+ </DataTableTHeadCell>
4000
+ <DataTableTHeadCell>
4001
+ <DataTableTHeadTitle>Category</DataTableTHeadTitle>
4002
+ </DataTableTHeadCell>
4003
+ <DataTableTHeadCell>
4004
+ <DataTableTHeadTitle>Price</DataTableTHeadTitle>
4005
+ </DataTableTHeadCell>
4006
+ <DataTableTHeadCell>
4007
+ <DataTableTHeadTitle>Status</DataTableTHeadTitle>
4008
+ </DataTableTHeadCell>
4009
+ </DataTableTHeadRow>
4010
+ </DataTableTHead>
4011
+ <DataTableTBody>
4012
+ {({ item }: { item: any }) => (
4013
+ <DataTableRow key={String(item.id)}>
4014
+ <DataTableCell>{String(item.name)}</DataTableCell>
4015
+ <DataTableCell>{String(item.category)}</DataTableCell>
4016
+ <DataTableCell>${String(item.price)}</DataTableCell>
4017
+ <DataTableCell>{String(item.status)}</DataTableCell>
4018
+ </DataTableRow>
4019
+ )}
4020
+ </DataTableTBody>
4021
+ <DataTableEmptyTBody>
4022
+ <DataTableRow>
4023
+ <DataTableCell colSpan={4}>
4024
+ <div className="flex flex-col items-center justify-center gap-3 py-10 text-center">
4025
+ <div className="w-14 h-14 rounded-full bg-surface-100 dark:bg-surface-800 flex items-center justify-center">
4026
+ <Inbox className="w-7 h-7 text-surface-400 dark:text-surface-500" />
4027
+ </div>
4028
+ <div>
4029
+ <p className="m-0 font-semibold text-surface-900 dark:text-surface-0">No products yet</p>
4030
+ <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">
4031
+ Add your first product to see it listed here.
4032
+ </p>
4033
+ </div>
4034
+ <Button size="small">
4035
+ <Plus />
4036
+ Add product
4037
+ </Button>
4038
+ </div>
4039
+ </DataTableCell>
4040
+ </DataTableRow>
4041
+ </DataTableEmptyTBody>
4042
+ </DataTableTable>
4043
+ </DataTableTableContainer>
4044
+ </DataTable>
4045
+ </div>
4046
+ );
4047
+ }
4048
+ ```
4049
+
4050
+ ## Related
4051
+
4052
+ ### Sub-Components
4053
+
4054
+ See [DataTable Primitive](/docs/primitive/components/datatable#api) for the full sub-component API.
4055
+
4056
+ ### Hooks
4057
+
4058
+ See [useDataTable](/docs/headless/components/datatable#api) for the headless hook API.
4059
+
4060
+ ### Accessibility
4061
+
4062
+ See [DataTable Primitive](/docs/primitive/components/datatable#accessibility) for WAI-ARIA compliance details and keyboard support.