@central-design-system/skills 4.0.0-alpha.6

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 (321) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/README.md +218 -0
  3. package/bin/cds-skills.mjs +124 -0
  4. package/package.json +33 -0
  5. package/skills/cds-builder/GENERATION.md +44 -0
  6. package/skills/cds-builder/SKILL.md +308 -0
  7. package/skills/cds-builder/references/component-decision-tree.md +310 -0
  8. package/skills/cds-builder/references/components/cds-app/api.md +19 -0
  9. package/skills/cds-builder/references/components/cds-app/tokens.md +12 -0
  10. package/skills/cds-builder/references/components/cds-autocomplete/api.md +155 -0
  11. package/skills/cds-builder/references/components/cds-autocomplete/examples.md +447 -0
  12. package/skills/cds-builder/references/components/cds-autocomplete/guidelines.md +10 -0
  13. package/skills/cds-builder/references/components/cds-autocomplete/tokens.md +19 -0
  14. package/skills/cds-builder/references/components/cds-avatar/api.md +35 -0
  15. package/skills/cds-builder/references/components/cds-avatar/examples.md +240 -0
  16. package/skills/cds-builder/references/components/cds-avatar/guidelines.md +10 -0
  17. package/skills/cds-builder/references/components/cds-avatar/tokens.md +32 -0
  18. package/skills/cds-builder/references/components/cds-badge/api.md +44 -0
  19. package/skills/cds-builder/references/components/cds-badge/examples.md +332 -0
  20. package/skills/cds-builder/references/components/cds-badge/guidelines.md +10 -0
  21. package/skills/cds-builder/references/components/cds-badge/tokens.md +32 -0
  22. package/skills/cds-builder/references/components/cds-breadcrumbs/api.md +83 -0
  23. package/skills/cds-builder/references/components/cds-breadcrumbs/examples.md +219 -0
  24. package/skills/cds-builder/references/components/cds-breadcrumbs/guidelines.md +10 -0
  25. package/skills/cds-builder/references/components/cds-breadcrumbs/tokens.md +24 -0
  26. package/skills/cds-builder/references/components/cds-button/api.md +66 -0
  27. package/skills/cds-builder/references/components/cds-button/examples.md +324 -0
  28. package/skills/cds-builder/references/components/cds-button/guidelines.md +10 -0
  29. package/skills/cds-builder/references/components/cds-button/tokens.md +190 -0
  30. package/skills/cds-builder/references/components/cds-button-group/api.md +27 -0
  31. package/skills/cds-builder/references/components/cds-button-group/examples.md +33 -0
  32. package/skills/cds-builder/references/components/cds-button-group/tokens.md +17 -0
  33. package/skills/cds-builder/references/components/cds-button-toggle/api.md +51 -0
  34. package/skills/cds-builder/references/components/cds-button-toggle/examples.md +278 -0
  35. package/skills/cds-builder/references/components/cds-button-toggle/guidelines.md +10 -0
  36. package/skills/cds-builder/references/components/cds-button-toggle/tokens.md +13 -0
  37. package/skills/cds-builder/references/components/cds-card/api.md +221 -0
  38. package/skills/cds-builder/references/components/cds-card/examples.md +652 -0
  39. package/skills/cds-builder/references/components/cds-card/guidelines.md +10 -0
  40. package/skills/cds-builder/references/components/cds-card/tokens.md +46 -0
  41. package/skills/cds-builder/references/components/cds-carousel/api.md +107 -0
  42. package/skills/cds-builder/references/components/cds-carousel/examples.md +444 -0
  43. package/skills/cds-builder/references/components/cds-carousel/guidelines.md +10 -0
  44. package/skills/cds-builder/references/components/cds-carousel/tokens.md +17 -0
  45. package/skills/cds-builder/references/components/cds-checkbox/api.md +93 -0
  46. package/skills/cds-builder/references/components/cds-checkbox/examples.md +319 -0
  47. package/skills/cds-builder/references/components/cds-checkbox/guidelines.md +10 -0
  48. package/skills/cds-builder/references/components/cds-checkbox/tokens.md +18 -0
  49. package/skills/cds-builder/references/components/cds-checkbox-button/api.md +56 -0
  50. package/skills/cds-builder/references/components/cds-checkbox-button/examples.md +29 -0
  51. package/skills/cds-builder/references/components/cds-checkbox-button/tokens.md +56 -0
  52. package/skills/cds-builder/references/components/cds-combobox/api.md +158 -0
  53. package/skills/cds-builder/references/components/cds-combobox/examples.md +264 -0
  54. package/skills/cds-builder/references/components/cds-combobox/guidelines.md +10 -0
  55. package/skills/cds-builder/references/components/cds-combobox/tokens.md +19 -0
  56. package/skills/cds-builder/references/components/cds-counter/api.md +24 -0
  57. package/skills/cds-builder/references/components/cds-counter/examples.md +93 -0
  58. package/skills/cds-builder/references/components/cds-counter/tokens.md +11 -0
  59. package/skills/cds-builder/references/components/cds-date-input/api.md +151 -0
  60. package/skills/cds-builder/references/components/cds-date-input/examples.md +479 -0
  61. package/skills/cds-builder/references/components/cds-date-input/guidelines.md +10 -0
  62. package/skills/cds-builder/references/components/cds-date-input/tokens.md +16 -0
  63. package/skills/cds-builder/references/components/cds-date-picker/api.md +63 -0
  64. package/skills/cds-builder/references/components/cds-date-picker/examples.md +394 -0
  65. package/skills/cds-builder/references/components/cds-date-picker/guidelines.md +10 -0
  66. package/skills/cds-builder/references/components/cds-date-picker/tokens.md +66 -0
  67. package/skills/cds-builder/references/components/cds-defaults-provider/api.md +21 -0
  68. package/skills/cds-builder/references/components/cds-description/api.md +22 -0
  69. package/skills/cds-builder/references/components/cds-description/examples.md +69 -0
  70. package/skills/cds-builder/references/components/cds-description/tokens.md +11 -0
  71. package/skills/cds-builder/references/components/cds-divider/api.md +29 -0
  72. package/skills/cds-builder/references/components/cds-divider/examples.md +374 -0
  73. package/skills/cds-builder/references/components/cds-divider/guidelines.md +10 -0
  74. package/skills/cds-builder/references/components/cds-divider/tokens.md +38 -0
  75. package/skills/cds-builder/references/components/cds-drawer/api.md +53 -0
  76. package/skills/cds-builder/references/components/cds-drawer/examples.md +432 -0
  77. package/skills/cds-builder/references/components/cds-drawer/guidelines.md +9 -0
  78. package/skills/cds-builder/references/components/cds-drawer/tokens.md +27 -0
  79. package/skills/cds-builder/references/components/cds-empty-state/api.md +55 -0
  80. package/skills/cds-builder/references/components/cds-empty-state/examples.md +330 -0
  81. package/skills/cds-builder/references/components/cds-empty-state/guidelines.md +9 -0
  82. package/skills/cds-builder/references/components/cds-empty-state/tokens.md +25 -0
  83. package/skills/cds-builder/references/components/cds-expandable-panel/api.md +157 -0
  84. package/skills/cds-builder/references/components/cds-expandable-panel/examples.md +569 -0
  85. package/skills/cds-builder/references/components/cds-expandable-panel/guidelines.md +10 -0
  86. package/skills/cds-builder/references/components/cds-expandable-panel/tokens.md +34 -0
  87. package/skills/cds-builder/references/components/cds-field/api.md +62 -0
  88. package/skills/cds-builder/references/components/cds-field/examples.md +202 -0
  89. package/skills/cds-builder/references/components/cds-field/tokens.md +94 -0
  90. package/skills/cds-builder/references/components/cds-file-input/api.md +109 -0
  91. package/skills/cds-builder/references/components/cds-file-input/examples.md +368 -0
  92. package/skills/cds-builder/references/components/cds-file-input/guidelines.md +10 -0
  93. package/skills/cds-builder/references/components/cds-file-input/tokens.md +15 -0
  94. package/skills/cds-builder/references/components/cds-file-upload/api.md +296 -0
  95. package/skills/cds-builder/references/components/cds-file-upload/examples.md +411 -0
  96. package/skills/cds-builder/references/components/cds-file-upload/guidelines.md +10 -0
  97. package/skills/cds-builder/references/components/cds-file-upload/tokens.md +50 -0
  98. package/skills/cds-builder/references/components/cds-footer/api.md +35 -0
  99. package/skills/cds-builder/references/components/cds-footer/examples.md +219 -0
  100. package/skills/cds-builder/references/components/cds-footer/guidelines.md +9 -0
  101. package/skills/cds-builder/references/components/cds-footer/tokens.md +14 -0
  102. package/skills/cds-builder/references/components/cds-form/api.md +48 -0
  103. package/skills/cds-builder/references/components/cds-form/examples.md +552 -0
  104. package/skills/cds-builder/references/components/cds-form/guidelines.md +10 -0
  105. package/skills/cds-builder/references/components/cds-form/tokens.md +15 -0
  106. package/skills/cds-builder/references/components/cds-functional-button/api.md +34 -0
  107. package/skills/cds-builder/references/components/cds-functional-button/examples.md +254 -0
  108. package/skills/cds-builder/references/components/cds-functional-button/guidelines.md +10 -0
  109. package/skills/cds-builder/references/components/cds-functional-button/tokens.md +35 -0
  110. package/skills/cds-builder/references/components/cds-grid/api.md +66 -0
  111. package/skills/cds-builder/references/components/cds-grid/examples.md +687 -0
  112. package/skills/cds-builder/references/components/cds-grid/guidelines.md +9 -0
  113. package/skills/cds-builder/references/components/cds-header/api.md +35 -0
  114. package/skills/cds-builder/references/components/cds-header/examples.md +345 -0
  115. package/skills/cds-builder/references/components/cds-header/guidelines.md +9 -0
  116. package/skills/cds-builder/references/components/cds-header/tokens.md +13 -0
  117. package/skills/cds-builder/references/components/cds-highlight/api.md +22 -0
  118. package/skills/cds-builder/references/components/cds-highlight/examples.md +313 -0
  119. package/skills/cds-builder/references/components/cds-highlight/guidelines.md +10 -0
  120. package/skills/cds-builder/references/components/cds-highlight/tokens.md +18 -0
  121. package/skills/cds-builder/references/components/cds-icon/api.md +33 -0
  122. package/skills/cds-builder/references/components/cds-icon/examples.md +272 -0
  123. package/skills/cds-builder/references/components/cds-icon/guidelines.md +10 -0
  124. package/skills/cds-builder/references/components/cds-icon/tokens.md +30 -0
  125. package/skills/cds-builder/references/components/cds-image/api.md +67 -0
  126. package/skills/cds-builder/references/components/cds-image/examples.md +437 -0
  127. package/skills/cds-builder/references/components/cds-image/guidelines.md +10 -0
  128. package/skills/cds-builder/references/components/cds-image/tokens.md +14 -0
  129. package/skills/cds-builder/references/components/cds-input/api.md +80 -0
  130. package/skills/cds-builder/references/components/cds-input/examples.md +205 -0
  131. package/skills/cds-builder/references/components/cds-input/tokens.md +108 -0
  132. package/skills/cds-builder/references/components/cds-label/api.md +25 -0
  133. package/skills/cds-builder/references/components/cds-label/examples.md +72 -0
  134. package/skills/cds-builder/references/components/cds-label/tokens.md +12 -0
  135. package/skills/cds-builder/references/components/cds-layout/api.md +50 -0
  136. package/skills/cds-builder/references/components/cds-layout/examples.md +431 -0
  137. package/skills/cds-builder/references/components/cds-layout/guidelines.md +9 -0
  138. package/skills/cds-builder/references/components/cds-layout/tokens.md +12 -0
  139. package/skills/cds-builder/references/components/cds-link/api.md +33 -0
  140. package/skills/cds-builder/references/components/cds-link/examples.md +195 -0
  141. package/skills/cds-builder/references/components/cds-link/guidelines.md +10 -0
  142. package/skills/cds-builder/references/components/cds-link/tokens.md +16 -0
  143. package/skills/cds-builder/references/components/cds-list/api.md +329 -0
  144. package/skills/cds-builder/references/components/cds-list/examples.md +653 -0
  145. package/skills/cds-builder/references/components/cds-list/guidelines.md +10 -0
  146. package/skills/cds-builder/references/components/cds-list/tokens.md +125 -0
  147. package/skills/cds-builder/references/components/cds-loader/api.md +18 -0
  148. package/skills/cds-builder/references/components/cds-loader/examples.md +59 -0
  149. package/skills/cds-builder/references/components/cds-loader/tokens.md +50 -0
  150. package/skills/cds-builder/references/components/cds-main/api.md +14 -0
  151. package/skills/cds-builder/references/components/cds-main/examples.md +60 -0
  152. package/skills/cds-builder/references/components/cds-menu/api.md +84 -0
  153. package/skills/cds-builder/references/components/cds-menu/examples.md +595 -0
  154. package/skills/cds-builder/references/components/cds-menu/guidelines.md +10 -0
  155. package/skills/cds-builder/references/components/cds-menu/tokens.md +19 -0
  156. package/skills/cds-builder/references/components/cds-messages/api.md +29 -0
  157. package/skills/cds-builder/references/components/cds-messages/examples.md +119 -0
  158. package/skills/cds-builder/references/components/cds-messages/tokens.md +15 -0
  159. package/skills/cds-builder/references/components/cds-modal/api.md +86 -0
  160. package/skills/cds-builder/references/components/cds-modal/examples.md +516 -0
  161. package/skills/cds-builder/references/components/cds-modal/guidelines.md +10 -0
  162. package/skills/cds-builder/references/components/cds-modal/tokens.md +22 -0
  163. package/skills/cds-builder/references/components/cds-notification/api.md +46 -0
  164. package/skills/cds-builder/references/components/cds-notification/examples.md +314 -0
  165. package/skills/cds-builder/references/components/cds-notification/guidelines.md +10 -0
  166. package/skills/cds-builder/references/components/cds-notification/tokens.md +23 -0
  167. package/skills/cds-builder/references/components/cds-notification-alert/api.md +93 -0
  168. package/skills/cds-builder/references/components/cds-notification-alert/examples.md +202 -0
  169. package/skills/cds-builder/references/components/cds-notification-alert/tokens.md +15 -0
  170. package/skills/cds-builder/references/components/cds-otp-input/api.md +106 -0
  171. package/skills/cds-builder/references/components/cds-otp-input/examples.md +329 -0
  172. package/skills/cds-builder/references/components/cds-otp-input/guidelines.md +10 -0
  173. package/skills/cds-builder/references/components/cds-otp-input/tokens.md +41 -0
  174. package/skills/cds-builder/references/components/cds-overlay/api.md +85 -0
  175. package/skills/cds-builder/references/components/cds-overlay/examples.md +409 -0
  176. package/skills/cds-builder/references/components/cds-overlay/guidelines.md +9 -0
  177. package/skills/cds-builder/references/components/cds-overlay/tokens.md +12 -0
  178. package/skills/cds-builder/references/components/cds-page/api.md +21 -0
  179. package/skills/cds-builder/references/components/cds-page/examples.md +123 -0
  180. package/skills/cds-builder/references/components/cds-pagination/api.md +67 -0
  181. package/skills/cds-builder/references/components/cds-pagination/examples.md +320 -0
  182. package/skills/cds-builder/references/components/cds-pagination/guidelines.md +10 -0
  183. package/skills/cds-builder/references/components/cds-pagination/tokens.md +20 -0
  184. package/skills/cds-builder/references/components/cds-progress-bar/api.md +53 -0
  185. package/skills/cds-builder/references/components/cds-progress-bar/examples.md +393 -0
  186. package/skills/cds-builder/references/components/cds-progress-bar/guidelines.md +10 -0
  187. package/skills/cds-builder/references/components/cds-progress-bar/tokens.md +33 -0
  188. package/skills/cds-builder/references/components/cds-progress-circle/api.md +30 -0
  189. package/skills/cds-builder/references/components/cds-progress-circle/examples.md +326 -0
  190. package/skills/cds-builder/references/components/cds-progress-circle/guidelines.md +10 -0
  191. package/skills/cds-builder/references/components/cds-progress-circle/tokens.md +31 -0
  192. package/skills/cds-builder/references/components/cds-radio/api.md +47 -0
  193. package/skills/cds-builder/references/components/cds-radio/tokens.md +36 -0
  194. package/skills/cds-builder/references/components/cds-radio-group/api.md +87 -0
  195. package/skills/cds-builder/references/components/cds-radio-group/examples.md +320 -0
  196. package/skills/cds-builder/references/components/cds-radio-group/guidelines.md +10 -0
  197. package/skills/cds-builder/references/components/cds-radio-group/tokens.md +14 -0
  198. package/skills/cds-builder/references/components/cds-responsive/api.md +29 -0
  199. package/skills/cds-builder/references/components/cds-responsive/examples.md +271 -0
  200. package/skills/cds-builder/references/components/cds-responsive/guidelines.md +10 -0
  201. package/skills/cds-builder/references/components/cds-responsive/tokens.md +13 -0
  202. package/skills/cds-builder/references/components/cds-select/api.md +155 -0
  203. package/skills/cds-builder/references/components/cds-select/examples.md +794 -0
  204. package/skills/cds-builder/references/components/cds-select/guidelines.md +10 -0
  205. package/skills/cds-builder/references/components/cds-select/tokens.md +17 -0
  206. package/skills/cds-builder/references/components/cds-selection-control/api.md +60 -0
  207. package/skills/cds-builder/references/components/cds-selection-control/examples.md +468 -0
  208. package/skills/cds-builder/references/components/cds-selection-control-group/api.md +45 -0
  209. package/skills/cds-builder/references/components/cds-selection-control-group/examples.md +349 -0
  210. package/skills/cds-builder/references/components/cds-skeleton-loader/api.md +30 -0
  211. package/skills/cds-builder/references/components/cds-skeleton-loader/examples.md +297 -0
  212. package/skills/cds-builder/references/components/cds-skeleton-loader/guidelines.md +10 -0
  213. package/skills/cds-builder/references/components/cds-skeleton-loader/tokens.md +62 -0
  214. package/skills/cds-builder/references/components/cds-slide-group/api.md +76 -0
  215. package/skills/cds-builder/references/components/cds-slide-group/examples.md +304 -0
  216. package/skills/cds-builder/references/components/cds-slide-group/guidelines.md +9 -0
  217. package/skills/cds-builder/references/components/cds-slide-group/tokens.md +11 -0
  218. package/skills/cds-builder/references/components/cds-slide-view/api.md +88 -0
  219. package/skills/cds-builder/references/components/cds-slide-view/examples.md +311 -0
  220. package/skills/cds-builder/references/components/cds-slide-view/guidelines.md +9 -0
  221. package/skills/cds-builder/references/components/cds-slide-view/tokens.md +14 -0
  222. package/skills/cds-builder/references/components/cds-stages/api.md +31 -0
  223. package/skills/cds-builder/references/components/cds-stages/examples.md +230 -0
  224. package/skills/cds-builder/references/components/cds-stages/guidelines.md +10 -0
  225. package/skills/cds-builder/references/components/cds-stages/tokens.md +27 -0
  226. package/skills/cds-builder/references/components/cds-status/api.md +24 -0
  227. package/skills/cds-builder/references/components/cds-status/examples.md +170 -0
  228. package/skills/cds-builder/references/components/cds-status/guidelines.md +10 -0
  229. package/skills/cds-builder/references/components/cds-status/tokens.md +17 -0
  230. package/skills/cds-builder/references/components/cds-steps/api.md +176 -0
  231. package/skills/cds-builder/references/components/cds-steps/examples.md +320 -0
  232. package/skills/cds-builder/references/components/cds-steps/guidelines.md +10 -0
  233. package/skills/cds-builder/references/components/cds-steps/tokens.md +52 -0
  234. package/skills/cds-builder/references/components/cds-surface/api.md +30 -0
  235. package/skills/cds-builder/references/components/cds-surface/examples.md +106 -0
  236. package/skills/cds-builder/references/components/cds-table/api.md +120 -0
  237. package/skills/cds-builder/references/components/cds-table/examples.md +3049 -0
  238. package/skills/cds-builder/references/components/cds-table/guidelines.md +10 -0
  239. package/skills/cds-builder/references/components/cds-table/tokens.md +92 -0
  240. package/skills/cds-builder/references/components/cds-tabs/api.md +179 -0
  241. package/skills/cds-builder/references/components/cds-tabs/examples.md +610 -0
  242. package/skills/cds-builder/references/components/cds-tabs/guidelines.md +10 -0
  243. package/skills/cds-builder/references/components/cds-tabs/tokens.md +40 -0
  244. package/skills/cds-builder/references/components/cds-tag/api.md +64 -0
  245. package/skills/cds-builder/references/components/cds-tag/examples.md +278 -0
  246. package/skills/cds-builder/references/components/cds-tag/guidelines.md +10 -0
  247. package/skills/cds-builder/references/components/cds-tag/tokens.md +67 -0
  248. package/skills/cds-builder/references/components/cds-tag-group/api.md +58 -0
  249. package/skills/cds-builder/references/components/cds-tag-group/examples.md +346 -0
  250. package/skills/cds-builder/references/components/cds-tag-group/guidelines.md +10 -0
  251. package/skills/cds-builder/references/components/cds-tag-group/tokens.md +18 -0
  252. package/skills/cds-builder/references/components/cds-text-area/api.md +113 -0
  253. package/skills/cds-builder/references/components/cds-text-area/examples.md +327 -0
  254. package/skills/cds-builder/references/components/cds-text-area/guidelines.md +10 -0
  255. package/skills/cds-builder/references/components/cds-text-area/tokens.md +16 -0
  256. package/skills/cds-builder/references/components/cds-text-input/api.md +115 -0
  257. package/skills/cds-builder/references/components/cds-text-input/examples.md +546 -0
  258. package/skills/cds-builder/references/components/cds-text-input/guidelines.md +10 -0
  259. package/skills/cds-builder/references/components/cds-text-input/tokens.md +17 -0
  260. package/skills/cds-builder/references/components/cds-time-picker/api.md +44 -0
  261. package/skills/cds-builder/references/components/cds-time-picker/examples.md +407 -0
  262. package/skills/cds-builder/references/components/cds-time-picker/guidelines.md +10 -0
  263. package/skills/cds-builder/references/components/cds-time-picker/tokens.md +23 -0
  264. package/skills/cds-builder/references/components/cds-toast/api.md +97 -0
  265. package/skills/cds-builder/references/components/cds-toast/examples.md +296 -0
  266. package/skills/cds-builder/references/components/cds-toast/guidelines.md +10 -0
  267. package/skills/cds-builder/references/components/cds-toast/tokens.md +26 -0
  268. package/skills/cds-builder/references/components/cds-toast-queue/api.md +86 -0
  269. package/skills/cds-builder/references/components/cds-toast-queue/examples.md +227 -0
  270. package/skills/cds-builder/references/components/cds-toggle/api.md +97 -0
  271. package/skills/cds-builder/references/components/cds-toggle/examples.md +335 -0
  272. package/skills/cds-builder/references/components/cds-toggle/guidelines.md +10 -0
  273. package/skills/cds-builder/references/components/cds-toggle/tokens.md +91 -0
  274. package/skills/cds-builder/references/components/cds-toolbar/api.md +44 -0
  275. package/skills/cds-builder/references/components/cds-toolbar/examples.md +186 -0
  276. package/skills/cds-builder/references/components/cds-toolbar/guidelines.md +9 -0
  277. package/skills/cds-builder/references/components/cds-toolbar/tokens.md +13 -0
  278. package/skills/cds-builder/references/components/cds-tooltip/api.md +83 -0
  279. package/skills/cds-builder/references/components/cds-tooltip/examples.md +321 -0
  280. package/skills/cds-builder/references/components/cds-tooltip/guidelines.md +10 -0
  281. package/skills/cds-builder/references/components/cds-tooltip/tokens.md +25 -0
  282. package/skills/cds-builder/references/components/cds-tree/api.md +169 -0
  283. package/skills/cds-builder/references/components/cds-tree/examples.md +785 -0
  284. package/skills/cds-builder/references/components/cds-tree/guidelines.md +10 -0
  285. package/skills/cds-builder/references/components/cds-tree/tokens.md +39 -0
  286. package/skills/cds-builder/references/components/cds-uploader/api.md +105 -0
  287. package/skills/cds-builder/references/components/cds-uploader/examples.md +553 -0
  288. package/skills/cds-builder/references/components/cds-uploader/guidelines.md +10 -0
  289. package/skills/cds-builder/references/components/cds-uploader/tokens.md +26 -0
  290. package/skills/cds-builder/references/components/cds-virtual-scroll/api.md +60 -0
  291. package/skills/cds-builder/references/components/cds-virtual-scroll/examples.md +272 -0
  292. package/skills/cds-builder/references/components/cds-virtual-scroll/guidelines.md +9 -0
  293. package/skills/cds-builder/references/components/cds-virtual-scroll/tokens.md +13 -0
  294. package/skills/cds-builder/references/guides/conventions.md +48 -0
  295. package/skills/cds-builder/references/guides/date-utils.md +301 -0
  296. package/skills/cds-builder/references/guides/display-and-platform.md +263 -0
  297. package/skills/cds-builder/references/guides/forms.md +77 -0
  298. package/skills/cds-builder/references/guides/global-configuration.md +354 -0
  299. package/skills/cds-builder/references/guides/icons.md +172 -0
  300. package/skills/cds-builder/references/guides/internationalization.md +470 -0
  301. package/skills/cds-builder/references/guides/migration-cds3-to-cds4.md +49 -0
  302. package/skills/cds-builder/references/guides/sass-variables.md +238 -0
  303. package/skills/cds-builder/references/guides/setup.md +81 -0
  304. package/skills/cds-builder/references/guides/tailwindcss.md +93 -0
  305. package/skills/cds-builder/references/guides/theme.md +343 -0
  306. package/skills/cds-builder/references/guides/treeshaking.md +237 -0
  307. package/skills/cds-builder/references/patterns/dialogs.md +89 -0
  308. package/skills/cds-builder/references/patterns/forms.md +101 -0
  309. package/skills/cds-builder/references/patterns/page-layout.md +78 -0
  310. package/skills/cds-builder/references/patterns/tables.md +87 -0
  311. package/skills/cds-builder/references/tokens/colors.md +507 -0
  312. package/skills/cds-builder/references/tokens/themes.md +981 -0
  313. package/skills/cds-builder/scripts/check-contracts.mjs +264 -0
  314. package/skills/cds-migrate/GENERATION.md +44 -0
  315. package/skills/cds-migrate/SKILL.md +160 -0
  316. package/skills/cds-migrate/references/migration-full.md +2225 -0
  317. package/skills/cds-refactor/GENERATION.md +44 -0
  318. package/skills/cds-refactor/SKILL.md +187 -0
  319. package/skills/cds-refactor/references/html-to-cds.md +80 -0
  320. package/skills/cds-refactor/references/review-checklist.md +73 -0
  321. package/skills/cds-refactor/references/tailwind-to-cds.md +65 -0
@@ -0,0 +1,3049 @@
1
+ # CdsTable — usage examples
2
+
3
+ > Extracted from Storybook stories (`docs/*.stories.ts`) by `bun run generate:skill`. Do not edit manually.
4
+ > The full component contract is in [api.md](api.md). `v-bind="args"` in templates refers to the props from the **Args** block of the corresponding example.
5
+
6
+ A table shows large sets of records in columns and takes the data work off your hands: sorting, filters, pagination, row selection and expansion, a pinned header and pinned columns. Columns are described by the `columns` prop, the records by the `data` prop.
7
+
8
+ ## Перетаскивание
9
+
10
+ ### Dragging rows
11
+
12
+ The `row-draggable` prop shows a grip icon to the left of the row. The move is reported by the `row-reorder` event — change the order in `data` yourself. A row can only be dropped between neighboring rows.
13
+
14
+ **Example (SFC):**
15
+
16
+ ```vue
17
+ <script setup lang="ts">
18
+ import type { NTable } from '@central-design-system/components';
19
+ import { CdsTable } from '@central-design-system/components';
20
+ import { ref } from 'vue';
21
+
22
+ type TRow = { key: string; name: string; category: string; price: string };
23
+
24
+ const columns: NTable.NColumn.TColumns<TRow> = [
25
+ { title: 'Наименование', dataIndex: 'name', key: 'name' },
26
+ { title: 'Категория', dataIndex: 'category', key: 'category' },
27
+ { title: 'Цена', dataIndex: 'price', key: 'price', align: 'right' }
28
+ ];
29
+
30
+ const data = ref<TRow[]>([
31
+ { key: '1', name: 'iPhone 17 Pro Max', category: 'Смартфоны', price: '$1700' },
32
+ { key: '2', name: 'MacBook Air M5', category: 'Ноутбуки', price: '$2000' },
33
+ { key: '3', name: 'Apple Vision Pro', category: 'Гарнитуры', price: '$3500' },
34
+ { key: '4', name: 'iPad Air', category: 'Планшеты', price: '$800' },
35
+ { key: '5', name: 'AirPods Pro', category: 'Наушники', price: '$250' }
36
+ ]);
37
+
38
+ const onRowReorder = (info: NTable.NDraggable.IRowReorderInfo<TRow>) => {
39
+ const next = [...data.value];
40
+ const dragIndex = next.findIndex((item) => item.key === info.dragKey);
41
+ if (dragIndex === -1) return;
42
+ const [moved] = next.splice(dragIndex, 1);
43
+ const dropIndex = next.findIndex((item) => item.key === info.dropKey);
44
+ next.splice(info.dropPosition === -1 ? dropIndex : dropIndex + 1, 0, moved);
45
+ data.value = next;
46
+ };
47
+ </script>
48
+
49
+ <template>
50
+ <CdsTable :columns="columns" :data="data" row-draggable :pagination="false" @row-reorder="onRowReorder" />
51
+ </template>
52
+ ```
53
+
54
+ ### Dragging nested rows
55
+
56
+ In a tree, a row is only reordered among the siblings of its own parent: it cannot be dropped onto another level — the cursor shows "not allowed" and no event arrives.
57
+
58
+ **Example (SFC):**
59
+
60
+ ```vue
61
+ <script setup lang="ts">
62
+ import type { NTable } from '@central-design-system/components';
63
+ import { CdsTable } from '@central-design-system/components';
64
+ import { ref } from 'vue';
65
+
66
+ type TRow = { key: number; name: string; price: number; children?: TRow[] };
67
+
68
+ const columns: NTable.NColumn.TColumns<TRow> = [
69
+ { title: 'Название', dataIndex: 'name', key: 'name' },
70
+ { title: 'Цена, $', dataIndex: 'price', key: 'price', align: 'right', width: '20%' }
71
+ ];
72
+
73
+ const data = ref<TRow[]>([
74
+ {
75
+ key: 1,
76
+ name: 'Apple iPhone',
77
+ price: 3250,
78
+ children: [
79
+ { key: 11, name: 'iPhone 14 Pro', price: 2000 },
80
+ { key: 12, name: 'Power Bank', price: 50 },
81
+ { key: 13, name: 'Headphones', price: 1200 }
82
+ ]
83
+ },
84
+ {
85
+ key: 2,
86
+ name: 'Xiaomi Mi',
87
+ price: 200,
88
+ children: [
89
+ { key: 21, name: 'Mi Band', price: 40 },
90
+ { key: 22, name: 'Mi Buds', price: 60 }
91
+ ]
92
+ }
93
+ ]);
94
+
95
+ // Находит массив-родитель, содержащий строку с заданным ключом.
96
+ const findParentList = (list: TRow[], key: NTable.TKey): TRow[] | null => {
97
+ for (const item of list) {
98
+ if (item.key === key) return list;
99
+ if (item.children) {
100
+ const found = findParentList(item.children, key);
101
+ if (found) return found;
102
+ }
103
+ }
104
+ return null;
105
+ };
106
+
107
+ const onRowReorder = (info: NTable.NDraggable.IRowReorderInfo<TRow>) => {
108
+ const next = [...data.value];
109
+ // drag и drop гарантированно в одном родителе (reorder только между соседями).
110
+ const parentList = findParentList(next, info.dragKey);
111
+ if (!parentList) return;
112
+ const dragIndex = parentList.findIndex((item) => item.key === info.dragKey);
113
+ const [moved] = parentList.splice(dragIndex, 1);
114
+ const dropIndex = parentList.findIndex((item) => item.key === info.dropKey);
115
+ parentList.splice(info.dropPosition === -1 ? dropIndex : dropIndex + 1, 0, moved);
116
+ data.value = next;
117
+ };
118
+ </script>
119
+
120
+ <template>
121
+ <CdsTable
122
+ :columns="columns"
123
+ :data="data"
124
+ :default-expand-all-rows="true"
125
+ row-draggable
126
+ :pagination="false"
127
+ @row-reorder="onRowReorder"
128
+ />
129
+ </template>
130
+ ```
131
+
132
+ ### Dragging columns
133
+
134
+ The `column-draggable` prop adds a grip icon to the header. The `column-reorder` event reports the new order — update the `columns` array.
135
+
136
+ **Example (SFC):**
137
+
138
+ ```vue
139
+ <script setup lang="ts">
140
+ import type { NTable } from '@central-design-system/components';
141
+ import { CdsTable } from '@central-design-system/components';
142
+ import { ref } from 'vue';
143
+
144
+ type TRow = { key: string; name: string; category: string; price: string; stock: string };
145
+
146
+ const columns = ref<NTable.NColumn.TColumns<TRow>>([
147
+ { title: 'Наименование', dataIndex: 'name', key: 'name' },
148
+ { title: 'Категория', dataIndex: 'category', key: 'category' },
149
+ { title: 'Цена', dataIndex: 'price', key: 'price', align: 'right' },
150
+ { title: 'Наличие', dataIndex: 'stock', key: 'stock' }
151
+ ]);
152
+
153
+ const data: TRow[] = [
154
+ { key: '1', name: 'iPhone 17 Pro Max', category: 'Смартфоны', price: '$1500', stock: 'В наличии' },
155
+ { key: '2', name: 'MacBook Air M5', category: 'Ноутбуки', price: '$2000', stock: 'В наличии' },
156
+ { key: '3', name: 'Apple Vision Pro', category: 'Гарнитуры', price: '$3500', stock: 'Нет в наличии' }
157
+ ];
158
+
159
+ const onColumnReorder = (info: NTable.NDraggable.IColumnReorderInfo<TRow>) => {
160
+ const next = [...columns.value];
161
+ const dragIndex = next.indexOf(info.dragColumn);
162
+ if (dragIndex === -1) return;
163
+ const [moved] = next.splice(dragIndex, 1);
164
+ const dropIndex = next.indexOf(info.dropColumn);
165
+ next.splice(info.dropPosition === -1 ? dropIndex : dropIndex + 1, 0, moved);
166
+ columns.value = next;
167
+ };
168
+ </script>
169
+
170
+ <template>
171
+ <CdsTable :columns="columns" :data="data" column-draggable :pagination="false" @column-reorder="onColumnReorder" />
172
+ </template>
173
+ ```
174
+
175
+ ### Dragging columns with groups
176
+
177
+ A leaf column is reordered inside its own group, while a group header is dragged as a whole among neighboring groups.
178
+
179
+ **Example (SFC):**
180
+
181
+ ```vue
182
+ <script setup lang="ts">
183
+ import type { NTable } from '@central-design-system/components';
184
+ import { CdsTable } from '@central-design-system/components';
185
+ import { ref } from 'vue';
186
+
187
+ const columns = ref<NTable.NColumn.TColumns>([
188
+ { title: 'Имя', dataIndex: 'name', key: 'name', width: 120 },
189
+ {
190
+ title: 'Контакты',
191
+ key: 'contacts',
192
+ children: [
193
+ { title: 'Телефон', dataIndex: 'phone', key: 'phone', width: 160 },
194
+ { title: 'Email', dataIndex: 'email', key: 'email', width: 200 }
195
+ ]
196
+ },
197
+ {
198
+ title: 'Компания',
199
+ key: 'company',
200
+ children: [
201
+ { title: 'Название', dataIndex: 'companyName', key: 'companyName', width: 160 },
202
+ { title: 'Город', dataIndex: 'city', key: 'city', width: 140 }
203
+ ]
204
+ }
205
+ ]);
206
+
207
+ const data = [...Array(5)].map((_, i) => ({
208
+ key: i,
209
+ name: 'Иван',
210
+ phone: '+7 900 000-00-00',
211
+ email: 'ivan@b2b-center.ru',
212
+ companyName: 'B2B-Center',
213
+ city: 'Москва'
214
+ }));
215
+
216
+ // Находит массив-родитель (уровень группы), содержащий колонку по ссылке.
217
+ const findParentList = (
218
+ list: NTable.NColumn.TColumns,
219
+ column: NTable.NColumn.IColumn
220
+ ): NTable.NColumn.TColumns | null => {
221
+ for (const col of list) {
222
+ if (col === column) return list;
223
+ const children = (col as NTable.NColumn.IColumnGroup).children;
224
+ if (children) {
225
+ const found = findParentList(children, column);
226
+ if (found) return found;
227
+ }
228
+ }
229
+ return null;
230
+ };
231
+
232
+ const onColumnReorder = (info: NTable.NDraggable.IColumnReorderInfo) => {
233
+ const next = [...columns.value];
234
+ const parentList = findParentList(next, info.dragColumn);
235
+ if (!parentList) return;
236
+ const dragIndex = parentList.indexOf(info.dragColumn);
237
+ const [moved] = parentList.splice(dragIndex, 1);
238
+ const dropIndex = parentList.indexOf(info.dropColumn);
239
+ parentList.splice(info.dropPosition === -1 ? dropIndex : dropIndex + 1, 0, moved);
240
+ columns.value = next;
241
+ };
242
+ </script>
243
+
244
+ <template>
245
+ <CdsTable
246
+ :columns="columns"
247
+ :data="data"
248
+ :scroll="{ x: 'max-content' }"
249
+ bordered
250
+ column-draggable
251
+ :pagination="false"
252
+ @column-reorder="onColumnReorder"
253
+ />
254
+ </template>
255
+ ```
256
+
257
+ ## Редактирование
258
+
259
+ Правка данных прямо в таблице: отдельные ячейки, построчный режим
260
+ с сохранением и отменой или редактирование всей таблицы разом.
261
+
262
+ ### Editable cells
263
+
264
+ The `bodyCell` slot replaces the content of a cell: on click the value turns into an input, on confirmation it turns back into text.
265
+
266
+ **Example (SFC):**
267
+
268
+ ```vue
269
+ <script setup lang="ts">
270
+ import type { NTable } from '@central-design-system/components';
271
+ import { CdsButton, CdsFunctionalButton, CdsTable, CdsTextInput, CdsTooltip } from '@central-design-system/components';
272
+ import type { Ref, UnwrapRef } from 'vue';
273
+ import { computed, reactive, ref } from 'vue';
274
+
275
+ type TDataItem = { key: string; name: string; age: number; address: string };
276
+
277
+ const columns: NTable.NColumn.TColumns<TDataItem> = [
278
+ { title: 'Имя', dataIndex: 'name', width: '30%' },
279
+ { title: 'Возраст', dataIndex: 'age' },
280
+ { title: 'Адрес', dataIndex: 'address' },
281
+ { title: 'Действия', dataIndex: 'actions' }
282
+ ];
283
+
284
+ const data: Ref<TDataItem[]> = ref([
285
+ { key: '0', name: 'Иван Иванов 0', age: 28, address: 'г. Москва, ул. Ленина, д. 0' },
286
+ { key: '1', name: 'Иван Иванов 1', age: 28, address: 'г. Москва, ул. Ленина, д. 1' }
287
+ ]);
288
+
289
+ const count = computed(() => data.value.length + 1);
290
+ const editableData: UnwrapRef<Record<string, TDataItem>> = reactive({});
291
+
292
+ function edit(key: string) {
293
+ editableData[key] = data.value.filter((item) => key === item.key)[0];
294
+ }
295
+
296
+ function save(key: string) {
297
+ Object.assign(data.value.filter((item) => key === item.key)[0], editableData[key]);
298
+ delete editableData[key];
299
+ }
300
+
301
+ function remove(key: string) {
302
+ data.value = data.value.filter((item) => item.key !== key);
303
+ }
304
+
305
+ const handleAdd = () => {
306
+ data.value.push({
307
+ key: `${count.value}`,
308
+ name: `Иван Иванов ${count.value}`,
309
+ age: 28,
310
+ address: `г. Москва, ул. Ленина, д. ${count.value}`
311
+ });
312
+ };
313
+ </script>
314
+
315
+ <template>
316
+ <CdsTable :columns="columns" :data="data">
317
+ <template #toolbar>
318
+ <div class="flex justify-end">
319
+ <CdsButton text="Добавить" @click="handleAdd" />
320
+ </div>
321
+ </template>
322
+
323
+ <template #bodyCell="{ column, text, record }">
324
+ <template v-if="column.dataIndex === 'name'">
325
+ <div class="flex">
326
+ <div v-if="editableData[record.key]" class="flex items-center grow-1">
327
+ <CdsTextInput
328
+ class="grow-1"
329
+ v-model="editableData[record.key].name"
330
+ hide-messages
331
+ @keydown.enter="save(record.key)"
332
+ />
333
+ <CdsTooltip location="right">
334
+ <template #activator="{ props }">
335
+ <CdsFunctionalButton icon="save" class="ml-2" v-bind="props" @click="save(record.key)" />
336
+ </template>
337
+ Сохранить
338
+ </CdsTooltip>
339
+ </div>
340
+ <div v-else class="flex items-center">
341
+ {{ text || ' ' }}
342
+ <CdsTooltip location="right">
343
+ <template #activator="{ props }">
344
+ <CdsFunctionalButton icon="edit" class="ml-2" v-bind="props" @click="edit(record.key)" />
345
+ </template>
346
+ Редактировать
347
+ </CdsTooltip>
348
+ </div>
349
+ </div>
350
+ </template>
351
+ <template v-else-if="column.dataIndex === 'actions'">
352
+ <CdsTooltip location="right">
353
+ <template #activator="{ props }">
354
+ <CdsFunctionalButton icon="delete" v-bind="props" @click="remove(record.key)" />
355
+ </template>
356
+ Удалить
357
+ </CdsTooltip>
358
+ </template>
359
+ </template>
360
+ </CdsTable>
361
+ </template>
362
+ ```
363
+
364
+ ### Editable rows
365
+
366
+ The whole row switches into edit mode: inputs in every cell, the "Save" and "Cancel" buttons in the actions column.
367
+
368
+ **Example (SFC):**
369
+
370
+ ```vue
371
+ <script setup lang="ts">
372
+ import type { NTable } from '@central-design-system/components';
373
+ import { CdsFunctionalButton, CdsTable, CdsTextInput, CdsTooltip } from '@central-design-system/components';
374
+ import type { UnwrapRef } from 'vue';
375
+ import { reactive, ref } from 'vue';
376
+
377
+ type TDataItem = { key: string; name: string; age: number; address: string };
378
+
379
+ const columns: NTable.NColumn.TColumns<TDataItem> = [
380
+ { title: 'Имя', dataIndex: 'name', width: '25%' },
381
+ { title: 'Возраст', dataIndex: 'age', width: '15%' },
382
+ { title: 'Адрес', dataIndex: 'address', width: '40%' },
383
+ { title: 'Действия', dataIndex: 'actions' }
384
+ ];
385
+
386
+ const data: TDataItem[] = [...Array(20)].map((_, i) => ({
387
+ key: i.toString(),
388
+ name: `Иван Иванов ${i}`,
389
+ age: 28,
390
+ address: `г. Москва, ул. Ленина, д. ${i}`
391
+ }));
392
+
393
+ const dataSource = ref(data);
394
+ const editableData: UnwrapRef<Record<string, TDataItem>> = reactive({});
395
+
396
+ function edit(key: string) {
397
+ editableData[key] = dataSource.value.filter((item) => key === item.key)[0];
398
+ }
399
+
400
+ function save(key: string) {
401
+ Object.assign(dataSource.value.filter((item) => key === item.key)[0], editableData[key]);
402
+ delete editableData[key];
403
+ }
404
+
405
+ function cancel(key: string) {
406
+ delete editableData[key];
407
+ }
408
+ </script>
409
+
410
+ <template>
411
+ <CdsTable :columns="columns" :data="dataSource">
412
+ <template #bodyCell="{ column, text, record }">
413
+ <template v-if="['name', 'age', 'address'].includes(String(column.dataIndex))">
414
+ <div>
415
+ <CdsTextInput
416
+ v-if="editableData[record.key]"
417
+ v-model="editableData[record.key][column.dataIndex as keyof TDataItem]"
418
+ hide-messages
419
+ />
420
+ <template v-else>
421
+ {{ text }}
422
+ </template>
423
+ </div>
424
+ </template>
425
+ <template v-else-if="column.dataIndex === 'actions'">
426
+ <div>
427
+ <span v-if="editableData[record.key]">
428
+ <CdsTooltip>
429
+ <template #activator="{ props }">
430
+ <CdsFunctionalButton icon="save" v-bind="props" @click="save(record.key)" />
431
+ </template>
432
+ Сохранить
433
+ </CdsTooltip>
434
+ <CdsTooltip>
435
+ <template #activator="{ props }">
436
+ <CdsFunctionalButton icon="remove" v-bind="props" @click="cancel(record.key)" />
437
+ </template>
438
+ Отменить
439
+ </CdsTooltip>
440
+ </span>
441
+ <span v-else>
442
+ <CdsTooltip location="left">
443
+ <template #activator="{ props }">
444
+ <CdsFunctionalButton icon="edit" v-bind="props" @click="edit(record.key)" />
445
+ </template>
446
+ Редактировать
447
+ </CdsTooltip>
448
+ </span>
449
+ </div>
450
+ </template>
451
+ </template>
452
+ </CdsTable>
453
+ </template>
454
+ ```
455
+
456
+ ### Editable table
457
+
458
+ Edit mode is turned on for the entire table at once — convenient for filling in reference data in bulk.
459
+
460
+ **Example (SFC):**
461
+
462
+ ```vue
463
+ <script setup lang="ts">
464
+ import type { NTable } from '@central-design-system/components';
465
+ import { CdsButton, CdsTable, CdsTextInput } from '@central-design-system/components';
466
+ import type { UnwrapRef } from 'vue';
467
+ import { reactive, ref } from 'vue';
468
+
469
+ type TDataItem = { key: string; name: string; age: number; address: string; quantity: number };
470
+
471
+ const columns: NTable.NColumn.TColumns<TDataItem> = [
472
+ { title: 'Имя', dataIndex: 'name', width: '25%' },
473
+ { title: 'Возраст', dataIndex: 'age', width: '15%' },
474
+ { title: 'Адрес', dataIndex: 'address', width: '40%' },
475
+ { title: 'Количество', dataIndex: 'quantity' }
476
+ ];
477
+
478
+ const data: TDataItem[] = [...Array(10)].map((_, i) => ({
479
+ key: i.toString(),
480
+ name: `Иван Иванов ${i}`,
481
+ age: 32,
482
+ address: `г. Москва, ул. Ленина, д. ${i}`,
483
+ quantity: Math.round(Math.random() * 100)
484
+ }));
485
+
486
+ const dataSource = ref(data);
487
+ const editing = ref(false);
488
+ const editableData: UnwrapRef<Record<string, TDataItem>> = reactive({});
489
+
490
+ function edit() {
491
+ if (!editing.value) {
492
+ editing.value = true;
493
+ dataSource.value.forEach((item) => {
494
+ editableData[item.key] = { ...item };
495
+ });
496
+ } else {
497
+ editing.value = false;
498
+ Object.assign(dataSource.value, editableData);
499
+ }
500
+ }
501
+
502
+ function cancel() {
503
+ editing.value = false;
504
+ dataSource.value.forEach((item) => {
505
+ delete editableData[item.key];
506
+ });
507
+ }
508
+ </script>
509
+
510
+ <template>
511
+ <CdsTable :columns="columns" :data="dataSource">
512
+ <template #toolbar>
513
+ <div class="flex justify-end">
514
+ <div class="flex gap-2">
515
+ <CdsButton appearance="secondary" :disabled="!editing" @click="cancel">Отменить</CdsButton>
516
+ <CdsButton @click="edit">{{ editing ? 'Сохранить' : 'Редактировать' }}</CdsButton>
517
+ </div>
518
+ </div>
519
+ </template>
520
+
521
+ <template #bodyCell="{ column, text, record }">
522
+ <div>
523
+ <CdsTextInput
524
+ v-if="editing"
525
+ v-model="editableData[record.key][column.dataIndex as keyof TDataItem]"
526
+ hide-messages
527
+ />
528
+ <template v-else>
529
+ {{ text }}
530
+ </template>
531
+ </div>
532
+ </template>
533
+ </CdsTable>
534
+ </template>
535
+ ```
536
+
537
+ ## Раскрытие и деревья
538
+
539
+ Дополнительное содержимое строки: раскрываемые строки со слотом,
540
+ вложенные таблицы и древовидные данные с каскадным выбором.
541
+
542
+ ### Expandable rows
543
+
544
+ The `expandedRowRender` slot renders the content of an expanded row — the column with the chevron appears automatically.
545
+
546
+ **Example (SFC):**
547
+
548
+ ```vue
549
+ <script setup lang="ts">
550
+ import type { NTable } from '@central-design-system/components';
551
+ import { CdsTable } from '@central-design-system/components';
552
+
553
+ type TDataType = { key: number; name: string; age: number; phone: string; address: string; description: string };
554
+
555
+ const columns: NTable.NColumn.TColumns<TDataType> = [
556
+ { title: 'Имя', dataIndex: 'name', key: 'name' },
557
+ { title: 'Возраст', dataIndex: 'age', key: 'age' },
558
+ { title: 'Адрес', dataIndex: 'address', key: 'address' },
559
+ { title: 'Телефон', dataIndex: 'phone', key: 'phone' }
560
+ ];
561
+ const data: TDataType[] = [
562
+ {
563
+ key: 1,
564
+ name: 'Иванов Иван',
565
+ age: 20,
566
+ phone: '79999999999',
567
+ address: 'г. Москва, ул. Ленина д. 1',
568
+ description: 'Меня зовут Иванов Иван, мне 20 лет и я живу по адресу г. Москва, ул. Ленина д. 1'
569
+ },
570
+ {
571
+ key: 2,
572
+ name: 'Иванов Василий',
573
+ age: 32,
574
+ phone: '79999999999',
575
+ address: 'г. Москва, ул. Ленина д. 2',
576
+ description: 'Меня зовут Иванов Василий, мне 32 года и я живу по адресу г. Москва, ул. Ленина д. 2'
577
+ },
578
+ {
579
+ key: 3,
580
+ name: 'Иванов Кирилл',
581
+ age: 41,
582
+ phone: '79999999999',
583
+ address: 'г. Москва, ул. Ленина д. 3',
584
+ description: 'Меня зовут Иванов Кирилл, мне 41 год и я живу по адресу г. Москва, ул. Ленина д. 3'
585
+ }
586
+ ];
587
+ </script>
588
+
589
+ <template>
590
+ <CdsTable :columns="columns" :data="data" :pagination="false">
591
+ <template #expandedRowRender="{ record }">
592
+ <p>{{ record.description }}</p>
593
+ </template>
594
+ </CdsTable>
595
+ </template>
596
+ ```
597
+
598
+ ### Nested tables
599
+
600
+ Anything can live inside `expandedRowRender` — including another `CdsTable` with its own columns and data.
601
+
602
+ **Example (SFC):**
603
+
604
+ ```vue
605
+ <script setup lang="ts">
606
+ import type { NTable } from '@central-design-system/components';
607
+ import { CdsFunctionalButton, CdsStatus, CdsTable, CdsToggle } from '@central-design-system/components';
608
+
609
+ const columns: NTable.NColumn.TColumns = [
610
+ { title: 'Название', dataIndex: 'name', key: 'name' },
611
+ { title: 'Платформа', dataIndex: 'platform', key: 'platform' },
612
+ { title: 'Версия', dataIndex: 'version', key: 'version' },
613
+ { title: 'Покрытие', dataIndex: 'coverage', key: 'coverage' },
614
+ { title: 'Автор', dataIndex: 'creator', key: 'creator' },
615
+ { title: 'Дата', dataIndex: 'createdAt', key: 'createdAt' },
616
+ { title: 'Действия', key: 'operation' }
617
+ ];
618
+ const data = [...Array(3)].map((_, i) => ({
619
+ key: i,
620
+ name: `Компонент ${i + 1}`,
621
+ platform: 'iOS',
622
+ version: '3.2.1',
623
+ coverage: 100,
624
+ creator: 'i.ivanov',
625
+ createdAt: new Date('2023-02-07 23:12:00').toLocaleDateString()
626
+ }));
627
+
628
+ const innerColumns: NTable.NColumn.TColumns = [
629
+ { title: 'Дата', dataIndex: 'date', key: 'date' },
630
+ { title: 'Название', dataIndex: 'name', key: 'name' },
631
+ { title: 'Статус', key: 'state' },
632
+ { title: 'Покрытие', dataIndex: 'coverage', key: 'coverage' },
633
+ { title: 'Действие', dataIndex: 'operation', key: 'operation' }
634
+ ];
635
+ const innerData = [...Array(3)].map((_, i) => ({
636
+ key: i,
637
+ date: new Date('2023-02-07 23:12:00').toLocaleDateString(),
638
+ name: `Это компонент ${i + 1}`,
639
+ coverage: 'Покрыто: 56'
640
+ }));
641
+ </script>
642
+
643
+ <template>
644
+ <CdsTable :columns="columns" :data="data" :pagination="false">
645
+ <template #bodyCell="{ column }">
646
+ <template v-if="column.key === 'operation'">
647
+ <CdsToggle hide-messages />
648
+ </template>
649
+ </template>
650
+ <template #expandedRowRender>
651
+ <CdsTable :columns="innerColumns" :data="innerData" :pagination="false">
652
+ <template #bodyCell="{ column }">
653
+ <template v-if="column.key === 'state'">
654
+ <span>
655
+ <CdsStatus status="success" text="Готово" />
656
+ </span>
657
+ </template>
658
+ <template v-else-if="column.key === 'operation'">
659
+ <span class="flex gap-2">
660
+ <CdsFunctionalButton icon="edit" />
661
+ <CdsFunctionalButton icon="delete" />
662
+ </span>
663
+ </template>
664
+ </template>
665
+ </CdsTable>
666
+ </template>
667
+ </CdsTable>
668
+ </template>
669
+ ```
670
+
671
+ ### Tree data
672
+
673
+ Records with a `children` field expand as a tree. Together with `row-selection`, the `checkStrictly: false` option turns on cascading selection: checking a parent selects its children.
674
+
675
+ **Example (SFC):**
676
+
677
+ ```vue
678
+ <script setup lang="ts">
679
+ import type { NTable } from '@central-design-system/components';
680
+ import { CdsCheckbox, CdsTable } from '@central-design-system/components';
681
+ import { ref } from 'vue';
682
+
683
+ type TDataItem = {
684
+ key: number;
685
+ name: string;
686
+ price: number;
687
+ description: string;
688
+ children?: TDataItem[];
689
+ };
690
+
691
+ const columns: NTable.NColumn.TColumns<TDataItem> = [
692
+ { title: 'Название', dataIndex: 'name', key: 'name' },
693
+ { title: 'Цена, $', dataIndex: 'price', key: 'price', width: '12%' },
694
+ { title: 'Описание', dataIndex: 'description', width: '30%', key: 'description' }
695
+ ];
696
+ const data: TDataItem[] = [
697
+ {
698
+ key: 1,
699
+ name: 'Apple iPhone',
700
+ price: 3250,
701
+ description: 'Описание товара',
702
+ children: [
703
+ { key: 11, name: 'iPhone 14 Pro', price: 2000, description: 'Описание товара' },
704
+ {
705
+ key: 12,
706
+ name: 'Power Bank',
707
+ price: 50,
708
+ description: 'Описание товара',
709
+ children: [{ key: 121, name: 'MagSafe Battery Pack', price: 50, description: 'Описание товара' }]
710
+ },
711
+ {
712
+ key: 13,
713
+ name: 'Headphones',
714
+ price: 1200,
715
+ description: 'Описание товара',
716
+ children: [
717
+ {
718
+ key: 131,
719
+ name: 'AirPods',
720
+ price: 1200,
721
+ description: 'Описание товара',
722
+ children: [
723
+ { key: 1311, name: 'AirPods Pro', price: 200, description: 'Описание товара' },
724
+ { key: 1312, name: 'AirPods Max', price: 1000, description: 'Описание товара' }
725
+ ]
726
+ }
727
+ ]
728
+ }
729
+ ]
730
+ },
731
+ { key: 2, name: 'Xiaomi Mi', price: 200, description: 'Описание товара' }
732
+ ];
733
+
734
+ const rowSelection = ref({
735
+ checkStrictly: false,
736
+ onChange: (selectedRowKeys: NTable.TKey[], selectedRows: TDataItem[]) => {
737
+ console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
738
+ },
739
+ onSelect: (record: TDataItem, selected: boolean, selectedRows: TDataItem[]) => {
740
+ console.log('onSelect', record, selected, selectedRows);
741
+ }
742
+ });
743
+
744
+ const rowClassName = (record: TDataItem) => (!record.children?.length ? 'cds-table__row--no-children' : '');
745
+ </script>
746
+
747
+ <template>
748
+ <div class="flex flex-col gap-4">
749
+ <CdsCheckbox v-model="rowSelection.checkStrictly" label="Строгий выбор" hide-messages />
750
+ <CdsTable :columns="columns" :data="data" :row-selection="rowSelection" :row-class-name="rowClassName" />
751
+ </div>
752
+ </template>
753
+ ```
754
+
755
+ ### Expanding every node
756
+
757
+ The `expanded-row-keys` prop makes expansion controlled: collect the keys of every parent record — and an "Expand all" button unfolds the whole tree.
758
+
759
+ **Example (SFC):**
760
+
761
+ ```vue
762
+ <script setup lang="ts">
763
+ import type { NTable } from '@central-design-system/components';
764
+ import { CdsFunctionalButton, CdsTable } from '@central-design-system/components';
765
+ import { computed, ref } from 'vue';
766
+
767
+ type TDataItem = {
768
+ key: number;
769
+ name: string;
770
+ price: number;
771
+ description: string;
772
+ children?: TDataItem[];
773
+ };
774
+
775
+ const columns: NTable.NColumn.TColumns<TDataItem> = [
776
+ { title: 'Название', dataIndex: 'name', key: 'name' },
777
+ { title: 'Цена, $', dataIndex: 'price', key: 'price', width: '12%' },
778
+ { title: 'Описание', dataIndex: 'description', width: '30%', key: 'description' }
779
+ ];
780
+ const data = ref<TDataItem[]>([
781
+ {
782
+ key: 1,
783
+ name: 'Apple iPhone',
784
+ price: 3250,
785
+ description: 'Описание товара',
786
+ children: [
787
+ { key: 11, name: 'iPhone 14 Pro', price: 2000, description: 'Описание товара' },
788
+ {
789
+ key: 12,
790
+ name: 'Power Bank',
791
+ price: 50,
792
+ description: 'Описание товара',
793
+ children: [{ key: 121, name: 'MagSafe Battery Pack', price: 50, description: 'Описание товара' }]
794
+ },
795
+ {
796
+ key: 13,
797
+ name: 'Headphones',
798
+ price: 1200,
799
+ description: 'Описание товара',
800
+ children: [
801
+ {
802
+ key: 131,
803
+ name: 'AirPods',
804
+ price: 1200,
805
+ description: 'Описание товара',
806
+ children: [
807
+ { key: 1311, name: 'AirPods Pro', price: 200, description: 'Описание товара' },
808
+ { key: 1312, name: 'AirPods Max', price: 1000, description: 'Описание товара' }
809
+ ]
810
+ }
811
+ ]
812
+ }
813
+ ]
814
+ },
815
+ { key: 2, name: 'Xiaomi Mi', price: 200, description: 'Описание товара' }
816
+ ]);
817
+
818
+ const rowClassName = (record: TDataItem) => (!record.children?.length ? 'cds-table__row--no-children' : '');
819
+
820
+ // Массив для хранения ключей развернутых узлов
821
+ const expandedRowKeys = ref<NTable.TKey[]>([]);
822
+
823
+ // Рекурсивная функция для сбора всех ключей из данных
824
+ const getAllKeys = (items: TDataItem[]): NTable.TKey[] => {
825
+ let keys: NTable.TKey[] = [];
826
+ items.forEach((item) => {
827
+ keys.push(item.key);
828
+ if (item.children && item.children.length > 0) {
829
+ keys = keys.concat(getAllKeys(item.children));
830
+ }
831
+ });
832
+ return keys;
833
+ };
834
+
835
+ const allRowKeys = computed(() => getAllKeys(data.value));
836
+
837
+ const allExpanded = computed(() => allRowKeys.value.every((key) => expandedRowKeys.value.includes(key)));
838
+
839
+ const toggleAll = () => {
840
+ expandedRowKeys.value = allExpanded.value ? [] : allRowKeys.value;
841
+ };
842
+
843
+ const handleExpand = (expanded: boolean, record: TDataItem) => {
844
+ if (expanded) {
845
+ expandedRowKeys.value.push(record.key);
846
+ } else {
847
+ expandedRowKeys.value = expandedRowKeys.value.filter((key) => key !== record.key);
848
+ }
849
+ };
850
+ </script>
851
+
852
+ <template>
853
+ <CdsTable
854
+ :columns="columns"
855
+ :data="data"
856
+ :expanded-row-keys="expandedRowKeys"
857
+ :row-class-name="rowClassName"
858
+ @expand="handleExpand"
859
+ >
860
+ <template #headerCell="{ title, column }">
861
+ <template v-if="column.key === 'name'">
862
+ <CdsFunctionalButton :icon="allExpanded ? 'chevron-down' : 'chevron-right'" @click="toggleAll" />
863
+ </template>
864
+ {{ title }}
865
+ </template>
866
+ </CdsTable>
867
+ </template>
868
+ ```
869
+
870
+ ## Фильтры и сортировка
871
+
872
+ Фильтры и сортировка описываются прямо в колонках: меню фильтра с
873
+ вложенными пунктами, сортировка по одной или нескольким колонкам,
874
+ управляемое состояние со сбросом и собственная панель фильтра.
875
+
876
+ ### Filters and sorting
877
+
878
+ The column filter menu is set by `filters` and `onFilter`, the sorting by the `sorter` function. `sortDirections` lists the directions, `defaultSortOrder` sets the initial order, and `filterMultiple: false` switches the filter to single choice. Every change is reported by the `change` event.
879
+
880
+ **Example (SFC):**
881
+
882
+ ```vue
883
+ <script setup lang="ts">
884
+ import type { ITableProps, NTable } from '@central-design-system/components';
885
+ import { CdsTable } from '@central-design-system/components';
886
+
887
+ type TDataType = { key: string; name: string; age: number; address: string };
888
+
889
+ const columns: NTable.NColumn.TColumns<TDataType> = [
890
+ {
891
+ title: 'Имя',
892
+ dataIndex: 'name',
893
+ filters: [
894
+ { text: 'Иван', value: 'Иван' },
895
+ { text: 'Петр', value: 'Петр' },
896
+ {
897
+ text: 'Фамилии',
898
+ value: 'Submenu',
899
+ children: [
900
+ { text: 'Иванов', value: 'Иван Иванов' },
901
+ { text: 'Петров', value: 'Петр Петров' }
902
+ ]
903
+ }
904
+ ],
905
+ onFilter: (value, record: TDataType) => record.name.indexOf(value as string) === 0,
906
+ sorter: (a: TDataType, b: TDataType) => a.name.length - b.name.length,
907
+ sortDirections: ['descend']
908
+ },
909
+ {
910
+ title: 'Возраст',
911
+ dataIndex: 'age',
912
+ defaultSortOrder: 'descend',
913
+ sorter: (a: TDataType, b: TDataType) => a.age - b.age
914
+ },
915
+ {
916
+ title: 'Адрес',
917
+ dataIndex: 'address',
918
+ filters: [
919
+ { text: 'Москва', value: 'Москва' },
920
+ { text: 'Санкт-Петербург', value: 'Санкт-Петербург' }
921
+ ],
922
+ filterMultiple: false,
923
+ onFilter: (value, record: TDataType) => record.address.indexOf(value as string) === 0,
924
+ sorter: (a: TDataType, b: TDataType) => a.address.length - b.address.length,
925
+ sortDirections: ['descend', 'ascend']
926
+ }
927
+ ];
928
+
929
+ const data: TDataType[] = [
930
+ { key: '1', name: 'Иван Иванов', age: 40, address: 'Москва, ул. Ленина, д. 1' },
931
+ { key: '2', name: 'Петр Петров', age: 42, address: 'Санкт-Петербург, ул. Ленина, д. 1' },
932
+ { key: '3', name: 'Иван Иванов', age: 18, address: 'Санкт-Петербург, ул. Ленина, д. 2' },
933
+ { key: '4', name: 'Петр Петров', age: 28, address: 'Москва, ул. Ленина, д. 2' }
934
+ ];
935
+
936
+ const onChange: ITableProps<TDataType>['onChange'] = (pagination, filters, sorter) => {
937
+ console.log('onChange', pagination, filters, sorter);
938
+ };
939
+ </script>
940
+
941
+ <template>
942
+ <CdsTable :columns="columns" :data="data" @change="onChange" />
943
+ </template>
944
+ ```
945
+
946
+ ### Multiple sorting
947
+
948
+ A `sorter` object with a `multiple` field allows sorting by several columns at once — the number sets the column's priority in the chain.
949
+
950
+ **Example (SFC):**
951
+
952
+ ```vue
953
+ <script setup lang="ts">
954
+ import type { ITableProps, NTable } from '@central-design-system/components';
955
+ import { CdsTable } from '@central-design-system/components';
956
+
957
+ type TDataType = { key: string; product: string; usd: number; eur: number; rub: number };
958
+
959
+ const columns: NTable.NColumn.TColumns<TDataType> = [
960
+ { title: 'Продукт', dataIndex: 'product' },
961
+ {
962
+ title: 'Цена USD',
963
+ dataIndex: 'usd',
964
+ sorter: { compare: (a, b) => a.usd - b.usd, multiple: 3 }
965
+ },
966
+ {
967
+ title: 'Цена EUR',
968
+ dataIndex: 'eur',
969
+ sorter: { compare: (a, b) => a.eur - b.eur, multiple: 2 }
970
+ },
971
+ {
972
+ title: 'Цена RUB',
973
+ dataIndex: 'rub',
974
+ sorter: { compare: (a, b) => a.rub - b.rub, multiple: 1 }
975
+ }
976
+ ];
977
+
978
+ const data: TDataType[] = [
979
+ { key: '1', product: 'Позиция 1', usd: 100, eur: 92, rub: 9100 },
980
+ { key: '2', product: 'Позиция 2', usd: 50, eur: 46, rub: 4530 },
981
+ { key: '3', product: 'Позиция 3', usd: 150, eur: 138, rub: 13590 },
982
+ { key: '4', product: 'Позиция 4', usd: 25, eur: 23, rub: 2265 }
983
+ ];
984
+
985
+ const onChange: ITableProps['onChange'] = (pagination, filters, sorter, extra) => {
986
+ console.log('params', pagination, filters, sorter, extra);
987
+ };
988
+ </script>
989
+
990
+ <template>
991
+ <CdsTable :columns="columns" :data="data" @change="onChange" />
992
+ </template>
993
+ ```
994
+
995
+ ### Resetting filters and sorting
996
+
997
+ The `filteredValue` and `sortOrder` column props make the state controlled: keep it outside, update it in the `change` handler — and reset it with buttons in a single assignment.
998
+
999
+ **Example (SFC):**
1000
+
1001
+ ```vue
1002
+ <script setup lang="ts">
1003
+ import type { ITableProps, NTable } from '@central-design-system/components';
1004
+ import { CdsButton, CdsTable } from '@central-design-system/components';
1005
+ import { computed, ref } from 'vue';
1006
+
1007
+ type TDataItem = { key: string; name: string; age: number; address: string };
1008
+
1009
+ const filteredInfo = ref();
1010
+ const sortedInfo = ref();
1011
+
1012
+ const columns = computed<NTable.NColumn.TColumns<TDataItem>>(() => {
1013
+ const filtered = filteredInfo.value || {};
1014
+ const sorted = sortedInfo.value || {};
1015
+
1016
+ return [
1017
+ {
1018
+ title: 'Имя',
1019
+ dataIndex: 'name',
1020
+ key: 'name',
1021
+ filters: [
1022
+ { text: 'Иван', value: 'Иван' },
1023
+ { text: 'Петр', value: 'Петр' }
1024
+ ],
1025
+ filteredValue: filtered.name || null,
1026
+ onFilter: (value, record: TDataItem) => record.name.includes(value as string),
1027
+ sorter: (a: TDataItem, b: TDataItem) => a.name.length - b.name.length,
1028
+ sortOrder: sorted.columnKey === 'name' && sorted.order,
1029
+ ellipsis: true
1030
+ },
1031
+ {
1032
+ title: 'Возраст',
1033
+ dataIndex: 'age',
1034
+ key: 'age',
1035
+ sorter: (a: TDataItem, b: TDataItem) => a.age - b.age,
1036
+ sortOrder: sorted.columnKey === 'age' && sorted.order
1037
+ },
1038
+ {
1039
+ title: 'Адрес',
1040
+ dataIndex: 'address',
1041
+ key: 'address',
1042
+ filters: [
1043
+ { text: 'Москва', value: 'Москва' },
1044
+ { text: 'Санкт-Петербург', value: 'Санкт-Петербург' }
1045
+ ],
1046
+ filteredValue: filtered.address || null,
1047
+ onFilter: (value, record: TDataItem) => record.address.includes(value as string),
1048
+ sorter: (a: TDataItem, b: TDataItem) => a.address.length - b.address.length,
1049
+ sortOrder: sorted.columnKey === 'address' && sorted.order,
1050
+ ellipsis: true
1051
+ }
1052
+ ] as NTable.NColumn.TColumns<TDataItem>;
1053
+ });
1054
+
1055
+ const data: TDataItem[] = [
1056
+ { key: '1', name: 'Иван Иванов', age: 40, address: 'Москва, ул. Ленина, д. 1' },
1057
+ { key: '2', name: 'Петр Петров', age: 42, address: 'Санкт-Петербург, ул. Ленина, д. 1' },
1058
+ { key: '3', name: 'Иван Иванов', age: 18, address: 'Санкт-Петербург, ул. Ленина, д. 2' },
1059
+ { key: '4', name: 'Петр Петров', age: 28, address: 'Москва, ул. Ленина, д. 2' }
1060
+ ];
1061
+
1062
+ const handleChange: ITableProps['onChange'] = (pagination, filters, sorter) => {
1063
+ filteredInfo.value = filters;
1064
+ sortedInfo.value = sorter;
1065
+ };
1066
+
1067
+ const clearFilters = () => {
1068
+ filteredInfo.value = null;
1069
+ };
1070
+
1071
+ const clearAll = () => {
1072
+ filteredInfo.value = null;
1073
+ sortedInfo.value = null;
1074
+ };
1075
+
1076
+ const setAgeSort = () => {
1077
+ sortedInfo.value = { order: 'descend', columnKey: 'age' };
1078
+ };
1079
+ </script>
1080
+
1081
+ <template>
1082
+ <CdsTable :columns="columns" :data="data" @change="handleChange">
1083
+ <template #toolbar>
1084
+ <div class="flex justify-end gap-2">
1085
+ <CdsButton appearance="secondary" @click="clearFilters">Очистить фильтры</CdsButton>
1086
+ <CdsButton appearance="secondary" @click="clearAll">Очистить фильтры и сортировки</CdsButton>
1087
+ <CdsButton appearance="primary" @click="setAgeSort">Сортировать возраст</CdsButton>
1088
+ </div>
1089
+ </template>
1090
+ </CdsTable>
1091
+ </template>
1092
+ ```
1093
+
1094
+ ### Custom filter panel
1095
+
1096
+ The column's `customFilterDropdown: true` flag enables the `customFilterDropdown` and `customFilterIcon` slots — the filter panel and its icon are drawn by your markup, a search field for example.
1097
+
1098
+ **Example (SFC):**
1099
+
1100
+ ```vue
1101
+ <script setup lang="ts">
1102
+ import type { NTable } from '@central-design-system/components';
1103
+ import { CdsButton, CdsDivider, CdsIcon, CdsTable, CdsTextInput } from '@central-design-system/components';
1104
+ import { reactive, ref, toRefs } from 'vue';
1105
+
1106
+ type TDataType = { key: string; name: string; age: number; address: string };
1107
+
1108
+ const state = reactive({
1109
+ searchText: '',
1110
+ searchedColumn: ''
1111
+ });
1112
+ const searchInput = ref();
1113
+
1114
+ const columns: NTable.NColumn.TColumns<TDataType> = [
1115
+ {
1116
+ title: 'Имя',
1117
+ dataIndex: 'name',
1118
+ key: 'name',
1119
+ customFilterDropdown: true,
1120
+ onFilter: (value, record) =>
1121
+ record.name
1122
+ .toString()
1123
+ .toLowerCase()
1124
+ .includes((value as string).toLowerCase()),
1125
+ onFilterDropdownVisibleChange: (visible) => {
1126
+ if (visible) {
1127
+ setTimeout(() => {
1128
+ searchInput.value?.focus?.();
1129
+ }, 100);
1130
+ }
1131
+ }
1132
+ },
1133
+ { title: 'Возраст', dataIndex: 'age', key: 'age' },
1134
+ {
1135
+ title: 'Адрес',
1136
+ dataIndex: 'address',
1137
+ key: 'address',
1138
+ customFilterDropdown: true,
1139
+ onFilter: (value, record) =>
1140
+ record.address
1141
+ .toString()
1142
+ .toLowerCase()
1143
+ .includes((value as string).toLowerCase()),
1144
+ onFilterDropdownVisibleChange: (visible) => {
1145
+ if (visible) {
1146
+ setTimeout(() => {
1147
+ searchInput.value?.focus?.();
1148
+ }, 100);
1149
+ }
1150
+ }
1151
+ }
1152
+ ];
1153
+ const data: TDataType[] = [
1154
+ { key: '1', name: 'Иван Иванов', age: 18, address: 'г. Москва ул. Ленина д. 1' },
1155
+ { key: '2', name: 'Петр Петров', age: 25, address: 'г. Санкт-Петербург ул. Ленина д. 1' },
1156
+ { key: '3', name: 'Александр Александрович', age: 48, address: 'г. Екатеринбург ул. Ленина д. 1' },
1157
+ { key: '4', name: 'Иван Петрович', age: 42, address: 'г. Новосибирск ул. Ленина д. 1' }
1158
+ ];
1159
+
1160
+ const handleSearch = (selectedKeys: NTable.TKey[], confirm: Function, dataIndex?: NTable.TDataIndex) => {
1161
+ confirm();
1162
+ state.searchText = String(selectedKeys[0] ?? '');
1163
+ state.searchedColumn = String(dataIndex ?? '');
1164
+ };
1165
+
1166
+ const handleReset = (clearFilters: ({ confirm }: { confirm: boolean }) => void) => {
1167
+ clearFilters({ confirm: true });
1168
+ state.searchText = '';
1169
+ };
1170
+
1171
+ const searchingRegExp = (searchText: string) => new RegExp(`(?<=${searchText})|(?=${searchText})`, 'i');
1172
+
1173
+ const { searchText, searchedColumn } = toRefs(state);
1174
+ </script>
1175
+
1176
+ <template>
1177
+ <CdsTable :columns="columns" :data="data">
1178
+ <template #customFilterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
1179
+ <div class="p-1">
1180
+ <cds-text-input
1181
+ ref="searchInput"
1182
+ :placeholder="'Введите ' + String(column?.title ?? '').toLowerCase()"
1183
+ :model-value="selectedKeys[0]"
1184
+ size="sm"
1185
+ clearable
1186
+ hide-messages
1187
+ class="w-[176px]"
1188
+ @update:model-value="(value: any) => setSelectedKeys(value ? [value] : [])"
1189
+ @keydown.enter="handleSearch(selectedKeys, confirm, column?.dataIndex)"
1190
+ />
1191
+ <cds-divider class="my-3" />
1192
+ <div class="flex gap-2">
1193
+ <cds-button text="Сбросить" size="sm" appearance="secondary" @click="handleReset(clearFilters)" />
1194
+ <cds-button
1195
+ text="Найти"
1196
+ size="sm"
1197
+ prepend-icon="search"
1198
+ @click="handleSearch(selectedKeys, confirm, column?.dataIndex)"
1199
+ />
1200
+ </div>
1201
+ </div>
1202
+ </template>
1203
+ <template #customFilterIcon>
1204
+ <cds-icon name="search" />
1205
+ </template>
1206
+ <template #bodyCell="{ text, column }">
1207
+ <span v-if="searchText && searchedColumn === column.dataIndex">
1208
+ <template v-for="(fragment, i) in text.toString().split(searchingRegExp(searchText))">
1209
+ <mark v-if="fragment.toLowerCase() === searchText.toLowerCase()" :key="i">
1210
+ {{ fragment }}
1211
+ </mark>
1212
+ <template v-else>{{ fragment }}</template>
1213
+ </template>
1214
+ </span>
1215
+ </template>
1216
+ </CdsTable>
1217
+ </template>
1218
+ ```
1219
+
1220
+ ## Фиксация и прокрутка
1221
+
1222
+ Работа с таблицами, которые не помещаются на экран: закрепление шапки
1223
+ и колонок при прокрутке и кнопки горизонтального скролла.
1224
+
1225
+ ### Fixed header
1226
+
1227
+ The `scroll.y` property limits the height of the table body — the column header stays in place while you scroll vertically.
1228
+
1229
+ **Example (SFC):**
1230
+
1231
+ ```vue
1232
+ <script setup lang="ts">
1233
+ import type { NTable } from '@central-design-system/components';
1234
+ import { CdsTable } from '@central-design-system/components';
1235
+
1236
+ type TDataType = { key: number; name: string; age: number; address: string };
1237
+
1238
+ const columns: NTable.NColumn.TColumns<TDataType> = [
1239
+ { title: 'Имя', dataIndex: 'name', width: 150 },
1240
+ { title: 'Возраст', dataIndex: 'age', width: 150 },
1241
+ { title: 'Адрес', dataIndex: 'address' }
1242
+ ];
1243
+
1244
+ const data: TDataType[] = [...Array(100)].map((_, i) => ({
1245
+ key: i,
1246
+ name: `Иван Иванов ${i}`,
1247
+ age: 27,
1248
+ address: `г. Москва, ул. Ленина, д. ${i}`
1249
+ }));
1250
+ </script>
1251
+
1252
+ <template>
1253
+ <CdsTable :columns="columns" :data="data" :pagination="{ total: 50 }" :scroll="{ y: 245 }" />
1254
+ </template>
1255
+ ```
1256
+
1257
+ ### Fixed columns
1258
+
1259
+ The column's `fixed: 'left' | 'right'` property pins it during horizontal scrolling. Set `scroll.x` wider than the container so that scrolling appears.
1260
+
1261
+ **Example (SFC):**
1262
+
1263
+ ```vue
1264
+ <script setup lang="ts">
1265
+ import type { NTable } from '@central-design-system/components';
1266
+ import { CdsButton, CdsTable } from '@central-design-system/components';
1267
+
1268
+ type TDataType = { key: string; name: string; age: number; address: string };
1269
+
1270
+ const columns: NTable.NColumn.TColumns<TDataType> = [
1271
+ { title: 'Имя', dataIndex: 'name', key: 'name', fixed: 'left', width: 100 },
1272
+ { title: 'Возраст', dataIndex: 'age', key: 'age', fixed: 'left', width: 100 },
1273
+ { title: 'Заголовок 1', dataIndex: 'address', key: '1' },
1274
+ { title: 'Заголовок 2', dataIndex: 'address', key: '2' },
1275
+ { title: 'Заголовок 3', dataIndex: 'address', key: '3' },
1276
+ { title: 'Заголовок 4', dataIndex: 'address', key: '4' },
1277
+ { title: 'Заголовок 5', dataIndex: 'address', key: '5' },
1278
+ { title: 'Заголовок 6', dataIndex: 'address', key: '6' },
1279
+ { title: 'Заголовок 7', dataIndex: 'address', key: '7' },
1280
+ { title: 'Заголовок 8', dataIndex: 'address', key: '8' },
1281
+ { title: 'Действия', key: 'actions', fixed: 'right', width: 100 }
1282
+ ];
1283
+
1284
+ const data: TDataType[] = [
1285
+ { key: '1', name: 'Иван', age: 32, address: 'г. Москва' },
1286
+ { key: '2', name: 'Петр', age: 40, address: 'г. Пермь' },
1287
+ { key: '3', name: 'Вадим', age: 32, address: 'г. Москва' },
1288
+ { key: '4', name: 'Евгений', age: 40, address: 'г. Балашиха' }
1289
+ ];
1290
+ </script>
1291
+
1292
+ <template>
1293
+ <CdsTable :columns="columns" :data="data" :scroll="{ x: 1300, y: 1000 }" :pagination="{ total: 2 }">
1294
+ <template #bodyCell="{ column }">
1295
+ <template v-if="column.key === 'actions'">
1296
+ <CdsButton size="sm" appearance="secondary">Удалить</CdsButton>
1297
+ </template>
1298
+ </template>
1299
+ </CdsTable>
1300
+ </template>
1301
+ ```
1302
+
1303
+ ### Fixed columns and header
1304
+
1305
+ Together, `scroll.x` and `scroll.y` pin both the columns and the header — the center of the table scrolls in both directions.
1306
+
1307
+ **Example (SFC):**
1308
+
1309
+ ```vue
1310
+ <script setup lang="ts">
1311
+ import type { NTable } from '@central-design-system/components';
1312
+ import { CdsButton, CdsTable } from '@central-design-system/components';
1313
+
1314
+ type TDataType = { key: number; name: string; age: number; address: string };
1315
+
1316
+ const columns: NTable.NColumn.TColumns<TDataType> = [
1317
+ { title: 'Имя', dataIndex: 'name', key: 'name', fixed: 'left', width: 100 },
1318
+ { title: 'Возраст', dataIndex: 'age', key: 'age', fixed: 'left', width: 100 },
1319
+ { title: 'Заголовок 1', dataIndex: 'address', key: '1', width: 150 },
1320
+ { title: 'Заголовок 2', dataIndex: 'address', key: '2', width: 150 },
1321
+ { title: 'Заголовок 3', dataIndex: 'address', key: '3', width: 150 },
1322
+ { title: 'Заголовок 4', dataIndex: 'address', key: '4', width: 150 },
1323
+ { title: 'Заголовок 5', dataIndex: 'address', key: '5', width: 150 },
1324
+ { title: 'Заголовок 6', dataIndex: 'address', key: '6', width: 150 },
1325
+ { title: 'Заголовок 7', dataIndex: 'address', key: '7', width: 150 },
1326
+ { title: 'Заголовок 8', dataIndex: 'address', key: '8', width: 150 },
1327
+ { title: 'Действия', key: 'actions', fixed: 'right', width: 100 }
1328
+ ];
1329
+
1330
+ const data: TDataType[] = [...Array(10)].map((_, i) => ({
1331
+ key: i,
1332
+ name: `Иван ${i}`,
1333
+ age: 24,
1334
+ address: `ул. Главная, д. ${i}`
1335
+ }));
1336
+ </script>
1337
+
1338
+ <template>
1339
+ <CdsTable :columns="columns" :data="data" :scroll="{ x: 1500, y: 300 }" :pagination="false">
1340
+ <template #bodyCell="{ column }">
1341
+ <template v-if="column.key === 'actions'">
1342
+ <CdsButton size="sm" appearance="secondary">Удалить</CdsButton>
1343
+ </template>
1344
+ </template>
1345
+ </CdsTable>
1346
+ </template>
1347
+ ```
1348
+
1349
+ ### Scroll buttons
1350
+
1351
+ A table with horizontal scrolling can get scroll buttons: take the DOM node of the table body (`.cds-table__body`) through the component `ref` and change its `scrollLeft` on click.
1352
+
1353
+ **Example (SFC):**
1354
+
1355
+ ```vue
1356
+ <script setup lang="ts">
1357
+ import type { NTable } from '@central-design-system/components';
1358
+ import { CdsButton, CdsTable } from '@central-design-system/components';
1359
+ import { ref, unref } from 'vue';
1360
+
1361
+ type TDataType = { key: string; name: string; age: number; address: string };
1362
+
1363
+ const columns: NTable.NColumn.TColumns<TDataType> = [
1364
+ { title: 'Имя', dataIndex: 'name', key: 'name', fixed: 'left', width: 100 },
1365
+ { title: 'Возраст', dataIndex: 'age', key: 'age', fixed: 'left', width: 100 },
1366
+ { title: 'Заголовок 1', dataIndex: 'address', key: '1' },
1367
+ { title: 'Заголовок 2', dataIndex: 'address', key: '2' },
1368
+ { title: 'Заголовок 3', dataIndex: 'address', key: '3' },
1369
+ { title: 'Заголовок 4', dataIndex: 'address', key: '4' },
1370
+ { title: 'Заголовок 5', dataIndex: 'address', key: '5' },
1371
+ { title: 'Заголовок 6', dataIndex: 'address', key: '6' },
1372
+ { title: 'Заголовок 7', dataIndex: 'address', key: '7' },
1373
+ { title: 'Заголовок 8', dataIndex: 'address', key: '8' },
1374
+ { title: 'Заголовок 9', dataIndex: 'address', key: '9' },
1375
+ { title: 'Заголовок 10', dataIndex: 'address', key: '10' },
1376
+ { title: 'Действия', key: 'actions', fixed: 'right', width: 100 }
1377
+ ];
1378
+
1379
+ const data: TDataType[] = [
1380
+ { key: '1', name: 'Иван', age: 32, address: 'г. Москва' },
1381
+ { key: '2', name: 'Петр', age: 40, address: 'г. Москва' }
1382
+ ];
1383
+
1384
+ // Функционал для реализации кнопок скролла
1385
+ const scrollStep = 50;
1386
+ const tableRef = ref<InstanceType<typeof CdsTable> | null>(null);
1387
+
1388
+ function handleClickScrollButton(_event: MouseEvent, isLeft: boolean) {
1389
+ const tableBody = unref(tableRef)?.$el.querySelector('.cds-table__body');
1390
+
1391
+ if (tableBody) {
1392
+ tableBody.scrollLeft += isLeft ? -scrollStep : scrollStep;
1393
+ }
1394
+ }
1395
+ </script>
1396
+
1397
+ <template>
1398
+ <CdsTable ref="tableRef" :columns="columns" :data="data" :scroll="{ x: 1300, y: 1000 }" :pagination="false">
1399
+ <template #header>
1400
+ <div class="flex justify-between">
1401
+ <div>
1402
+ <h4 class="mb-2">Заголовок таблицы</h4>
1403
+ <p class="text-content-secondary">Описание таблицы в слоте заголовка таблицы</p>
1404
+ </div>
1405
+ <div>
1406
+ <CdsButton text="Действие" />
1407
+ </div>
1408
+ </div>
1409
+ </template>
1410
+ <template #toolbar>
1411
+ <div class="flex justify-end">
1412
+ <div class="flex gap-2">
1413
+ <CdsButton icon="chevron-left" appearance="tertiary" @click="(e) => handleClickScrollButton(e, true)" />
1414
+ <CdsButton icon="chevron-right" appearance="tertiary" @click="(e) => handleClickScrollButton(e, false)" />
1415
+ </div>
1416
+ </div>
1417
+ </template>
1418
+
1419
+ <template #bodyCell="{ column }">
1420
+ <template v-if="column.key === 'actions'">
1421
+ <CdsButton appearance="secondary" size="sm">Удалить</CdsButton>
1422
+ </template>
1423
+ </template>
1424
+
1425
+ <template #footer>
1426
+ <div class="flex justify-end gap-2">
1427
+ <CdsButton icon="chevron-left" appearance="tertiary" @click="(e) => handleClickScrollButton(e, true)" />
1428
+ <CdsButton icon="chevron-right" appearance="tertiary" @click="(e) => handleClickScrollButton(e, false)" />
1429
+ </div>
1430
+ </template>
1431
+ </CdsTable>
1432
+ </template>
1433
+ ```
1434
+
1435
+ ## Сценарии
1436
+
1437
+ Готовые рецепты продуктовых таблиц: данные с сервера, модерация со
1438
+ сложной шапкой, встроенная пагинация со скелетоном и вложенные списки.
1439
+
1440
+ ### Data from the server
1441
+
1442
+ Pagination, sorting and filters are handled on the backend: the `change` event gives you the current state of the table, the handler requests a page of data, and the `loading` prop shows the request in progress.
1443
+
1444
+ **Example (SFC):**
1445
+
1446
+ ```vue
1447
+ <script setup lang="ts">
1448
+ import type { ITableProps } from '@central-design-system/components';
1449
+ import { CdsTable } from '@central-design-system/components';
1450
+ import { computed, onMounted, ref } from 'vue';
1451
+
1452
+ type APIResponse = {
1453
+ results: {
1454
+ gender: 'female' | 'male';
1455
+ name: { first: string; last: string };
1456
+ email: string;
1457
+ login: { uuid: string };
1458
+ location: { city: string; country: string };
1459
+ }[];
1460
+ };
1461
+
1462
+ const columns = [
1463
+ { title: 'Имя', dataIndex: 'name', sorter: true, width: '20%' },
1464
+ {
1465
+ title: 'Пол',
1466
+ dataIndex: 'gender',
1467
+ filters: [
1468
+ { text: 'Мужчина', value: 'male' },
1469
+ { text: 'Женщина', value: 'female' }
1470
+ ],
1471
+ width: '20%'
1472
+ },
1473
+ { title: 'Email', dataIndex: 'email' },
1474
+ { title: 'Адрес', dataIndex: 'location', sorter: true }
1475
+ ];
1476
+ const dataSource = ref<APIResponse['results']>([]);
1477
+
1478
+ const FIRST_PAGE = 1;
1479
+ const PER_PAGE = 10;
1480
+ const TOTAL = 100;
1481
+ const pagination = computed(() => ({
1482
+ total: TOTAL,
1483
+ length: PER_PAGE
1484
+ }));
1485
+
1486
+ const isLoading = ref(false);
1487
+
1488
+ async function queryData(params: Record<string, any>) {
1489
+ try {
1490
+ isLoading.value = true;
1491
+
1492
+ const query = new URLSearchParams(
1493
+ Object.entries(params)
1494
+ .filter(([, value]) => value !== undefined && value !== null)
1495
+ .map(([key, value]) => [key, String(value)])
1496
+ );
1497
+ const response = await fetch(`https://randomuser.me/api?${query}`);
1498
+ const data: APIResponse = await response.json();
1499
+
1500
+ dataSource.value = data.results ?? [];
1501
+ } catch (error) {
1502
+ console.error(error);
1503
+ } finally {
1504
+ isLoading.value = false;
1505
+ }
1506
+ }
1507
+
1508
+ onMounted(() => {
1509
+ queryData({ results: PER_PAGE, page: FIRST_PAGE });
1510
+ });
1511
+
1512
+ const handleTableChange: ITableProps['onChange'] = (paginationInfo, filters: any, sorter: any) => {
1513
+ queryData({
1514
+ results: paginationInfo.length as number,
1515
+ page: paginationInfo.modelValue,
1516
+ sortField: sorter.field,
1517
+ sortOrder: sorter.order,
1518
+ ...filters
1519
+ });
1520
+ };
1521
+ </script>
1522
+
1523
+ <template>
1524
+ <CdsTable
1525
+ :data="dataSource"
1526
+ :columns="columns"
1527
+ :row-key="(record) => record.login.uuid"
1528
+ :pagination="pagination"
1529
+ :loading="isLoading"
1530
+ @change="handleTableChange"
1531
+ >
1532
+ <template #bodyCell="{ column, text }">
1533
+ <template v-if="column.dataIndex === 'name'">{{ text.first }} {{ text.last }}</template>
1534
+ <template v-if="column.dataIndex === 'location'">{{ text.country }}, {{ text.city }}</template>
1535
+ </template>
1536
+ </CdsTable>
1537
+ </template>
1538
+ ```
1539
+
1540
+ ### Product moderation
1541
+
1542
+ A header with tabs, a toolbar with search and filters, an empty state with an action and external pagination in the footer — a typical table for a moderation section.
1543
+
1544
+ **Example (SFC):**
1545
+
1546
+ ```vue
1547
+ <script setup lang="ts">
1548
+ import type { NTable } from '@central-design-system/components';
1549
+ import {
1550
+ CdsBadge,
1551
+ CdsButton,
1552
+ CdsEmptyState,
1553
+ CdsFunctionalButton,
1554
+ CdsIcon,
1555
+ CdsLoader,
1556
+ CdsPagination,
1557
+ CdsSelect,
1558
+ CdsSkeletonLoader,
1559
+ CdsTab,
1560
+ CdsTable,
1561
+ CdsTabs,
1562
+ CdsTag,
1563
+ CdsTextInput,
1564
+ CdsTooltip,
1565
+ utils
1566
+ } from '@central-design-system/components';
1567
+ import { computed, onMounted, ref, unref, watch } from 'vue';
1568
+
1569
+ const { debounce, plurals } = utils;
1570
+
1571
+ type TDataItem = {
1572
+ key: number;
1573
+ sku: string;
1574
+ date: string;
1575
+ name: string;
1576
+ category: string;
1577
+ tolerance: number;
1578
+ count: string;
1579
+ base_price: number | string;
1580
+ percent: number;
1581
+ price: number | string;
1582
+ };
1583
+
1584
+ const columns: NTable.NColumn.TColumns<TDataItem> = [
1585
+ { title: 'Артикул', dataIndex: 'sku', width: 140 },
1586
+ { title: 'Дата', dataIndex: 'date', width: 140 },
1587
+ { title: 'Наименование', dataIndex: 'name', width: 350 },
1588
+ { title: 'Толеранс, %', dataIndex: 'tolerance', align: 'right', width: 110 },
1589
+ { title: 'Единица', dataIndex: 'count', width: 110 },
1590
+ { title: 'Базовая цена, ₽', dataIndex: 'base_price', align: 'right', width: 140 },
1591
+ { title: '%', dataIndex: 'percent', align: 'right', width: 70 },
1592
+ { title: 'Новая цена, ₽', dataIndex: 'price', align: 'right', width: 140 }
1593
+ ];
1594
+ const data = ref<TDataItem[]>([]);
1595
+ const filtersData = ['Узлы, устройства и инструменты', 'Изделия производственные', 'Машины установки и оборудование'];
1596
+ const isLoading = ref(false);
1597
+ const tableDataSwitcher = ref(0);
1598
+ const isActiveFilters = ref(false);
1599
+
1600
+ const currentPage = ref(1);
1601
+ const totalData = ref(100);
1602
+ const perPage = ref(10);
1603
+ const pagesLength = computed(() => Math.ceil(unref(totalData) / unref(perPage)));
1604
+ const pageSizeItems = Array.from(new Set([5, 10, 15, 20, 30, 50]), (item) => ({
1605
+ value: item,
1606
+ title: item.toString()
1607
+ }));
1608
+
1609
+ const queryParams = computed(() => ({
1610
+ page: unref(currentPage),
1611
+ perPage: unref(perPage)
1612
+ }));
1613
+
1614
+ const formatPrice = (price: number | string) =>
1615
+ new Intl.NumberFormat('ru-RU', {
1616
+ style: 'decimal',
1617
+ minimumFractionDigits: 2,
1618
+ maximumFractionDigits: 2
1619
+ }).format(+price);
1620
+
1621
+ const queryData = async (params?: Record<string, any>) => {
1622
+ try {
1623
+ isLoading.value = true;
1624
+
1625
+ const query = new URLSearchParams({ example: 'example1', ...params });
1626
+ const response = await fetch(`https://cds-api.b2b-center.ru/api/v1/examples/data-table?${query}`);
1627
+ const { data: examples, meta: metaInfo } = await response.json();
1628
+ // Принудительная задержка для примера загрузки
1629
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1630
+
1631
+ totalData.value = metaInfo.total;
1632
+ perPage.value = metaInfo.perPage;
1633
+
1634
+ data.value =
1635
+ examples.map((item: TDataItem, idx: number) => ({
1636
+ ...item,
1637
+ base_price: formatPrice(item.base_price),
1638
+ price: formatPrice(item.price),
1639
+ key: idx
1640
+ })) ?? [];
1641
+ } catch (error) {
1642
+ console.error(error);
1643
+ } finally {
1644
+ isLoading.value = false;
1645
+ }
1646
+ };
1647
+
1648
+ const getDataTableExample = () => {
1649
+ queryData(unref(queryParams));
1650
+ };
1651
+ const clearDataTableExample = () => {
1652
+ data.value = [];
1653
+ };
1654
+
1655
+ const isPublishLoading = ref(false);
1656
+ const publishData = async () => {
1657
+ isPublishLoading.value = true;
1658
+
1659
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1660
+ selectedRowKeys.value = [];
1661
+
1662
+ isPublishLoading.value = false;
1663
+ };
1664
+
1665
+ const selectedRowKeys = ref<NTable.TKey[]>([]);
1666
+ const selectedRowText = computed(() => plurals(unref(selectedRowKeys).length, ['строка', 'строки', 'строк']));
1667
+
1668
+ const cancelSelected = () => {
1669
+ selectedRowKeys.value = [];
1670
+ };
1671
+
1672
+ const onSelectChange = (selectedKeys: NTable.TKey[]) => {
1673
+ selectedRowKeys.value = selectedKeys;
1674
+ };
1675
+
1676
+ const handleSearch = debounce(async () => {
1677
+ await queryData(unref(queryParams));
1678
+ }, 500);
1679
+
1680
+ onMounted(() => {
1681
+ queryData(unref(queryParams));
1682
+ });
1683
+
1684
+ watch([currentPage, perPage], () => {
1685
+ queryData(unref(queryParams));
1686
+ });
1687
+
1688
+ watch(tableDataSwitcher, () => {
1689
+ queryData(unref(queryParams));
1690
+ });
1691
+ </script>
1692
+
1693
+ <template>
1694
+ <CdsTable
1695
+ class="min-w-[860px]"
1696
+ :columns="columns"
1697
+ :data="data"
1698
+ :row-selection="{ selectedRowKeys, onChange: onSelectChange }"
1699
+ :pagination="false"
1700
+ >
1701
+ <template #header>
1702
+ <div class="flex justify-between">
1703
+ <div>
1704
+ <h4>Список товаров, поступивших на модерацию</h4>
1705
+ <p class="mb-4 text-content-secondary">
1706
+ Согласуйте или отклоните товары поставщика для их доступности в каталоге
1707
+ </p>
1708
+ <div class="flex">
1709
+ <CdsTabs v-model="tableDataSwitcher">
1710
+ <CdsTab>Все 654</CdsTab>
1711
+ <CdsTab>Новые 340</CdsTab>
1712
+ <CdsTab>Обновленные 314</CdsTab>
1713
+ </CdsTabs>
1714
+ </div>
1715
+ </div>
1716
+ <div>
1717
+ <CdsButton appearance="secondary" text="Удалить данные" @click="clearDataTableExample" />
1718
+ </div>
1719
+ </div>
1720
+ </template>
1721
+
1722
+ <template #toolbar>
1723
+ <div class="flex items-center justify-between">
1724
+ <div class="flex items-center gap-4">
1725
+ <p>
1726
+ Выбрано: <strong>{{ selectedRowKeys.length }}</strong> {{ selectedRowText }}
1727
+ </p>
1728
+ <CdsTextInput
1729
+ prepend-inner-icon="search"
1730
+ placeholder="Артикул, наименование"
1731
+ hide-messages
1732
+ class="w-[250px]"
1733
+ @update:model-value="handleSearch"
1734
+ />
1735
+ <CdsButton
1736
+ :appearance="isActiveFilters ? 'secondary' : 'tertiary'"
1737
+ prepend-icon="filters"
1738
+ @click="isActiveFilters = !isActiveFilters"
1739
+ >
1740
+ Фильтры
1741
+ <CdsBadge v-if="isActiveFilters" content="10" inline />
1742
+ </CdsButton>
1743
+ </div>
1744
+ <div class="flex gap-2">
1745
+ <CdsButton appearance="secondary" text="Отклонить" @click="cancelSelected" />
1746
+ <CdsButton text="Опубликовать" :loading="isPublishLoading" @click="publishData" />
1747
+ </div>
1748
+ </div>
1749
+ <div v-if="isActiveFilters" class="mt-4">
1750
+ <h6 class="mb-2">Активные фильтры:</h6>
1751
+ <div class="flex flex-col gap-2">
1752
+ <div class="flex">
1753
+ <div class="flex items-start">
1754
+ <CdsFunctionalButton icon="remove" @click="isActiveFilters = false" />
1755
+ <div class="mx-1 flex h-[24px] items-center">Категории:</div>
1756
+ </div>
1757
+ <div class="flex flex-wrap items-center gap-2">
1758
+ <CdsTag v-for="item in filtersData" :key="item">{{ item }}</CdsTag>
1759
+ </div>
1760
+ </div>
1761
+ </div>
1762
+ </div>
1763
+ </template>
1764
+
1765
+ <template #headerCell="{ title, column }">
1766
+ <div class="flex items-center" :class="{ 'justify-end': column.align === 'right' }">
1767
+ <span>{{ title }}</span>
1768
+ <CdsTooltip v-if="['count', 'percent'].includes(String(column.dataIndex))">
1769
+ <template #activator="{ props }">
1770
+ <CdsIcon name="info" class="ml-2" v-bind="props" />
1771
+ </template>
1772
+ Информация о столбце
1773
+ </CdsTooltip>
1774
+ </div>
1775
+ </template>
1776
+
1777
+ <template #bodyCell>
1778
+ <template v-if="isLoading">
1779
+ <CdsSkeletonLoader type="text" />
1780
+ </template>
1781
+ </template>
1782
+
1783
+ <template #footer>
1784
+ <div class="flex items-center justify-between">
1785
+ <CdsPagination v-model="currentPage" :length="pagesLength" class="w-full" />
1786
+ <div class="flex w-full justify-center gap-1">
1787
+ Отображено <strong>{{ perPage }}</strong> из <strong>{{ totalData }}</strong>
1788
+ </div>
1789
+ <div class="flex w-full items-center justify-end gap-2">
1790
+ <span class="text-content-tertiary">Строк на странице:</span>
1791
+ <CdsSelect v-model="perPage" :items="pageSizeItems" hide-messages class="max-w-[70px]" />
1792
+ </div>
1793
+ </div>
1794
+ </template>
1795
+
1796
+ <template #emptyText>
1797
+ <div class="flex h-[500px] items-center justify-center">
1798
+ <CdsLoader v-if="isLoading" fixed overlay />
1799
+ <CdsEmptyState
1800
+ title="Нет данных"
1801
+ description="Нажмите кнопку ниже чтобы добавить данные в таблицу"
1802
+ illustration="documents"
1803
+ >
1804
+ <template #actions>
1805
+ <CdsButton text="Добавить данные" @click="getDataTableExample" />
1806
+ </template>
1807
+ </CdsEmptyState>
1808
+ </div>
1809
+ </template>
1810
+ </CdsTable>
1811
+ </template>
1812
+ ```
1813
+
1814
+ ### Items and prices
1815
+
1816
+ Built-in pagination with a counter and a page size selector, a skeleton while loading and composite cells with an extra line of text.
1817
+
1818
+ **Example (SFC):**
1819
+
1820
+ ```vue
1821
+ <script setup lang="ts">
1822
+ import type { ITableProps, NTable } from '@central-design-system/components';
1823
+ import {
1824
+ CdsButton,
1825
+ CdsEmptyState,
1826
+ CdsLoader,
1827
+ CdsSkeletonLoader,
1828
+ CdsTab,
1829
+ CdsTable,
1830
+ CdsTabs,
1831
+ CdsTextInput,
1832
+ utils
1833
+ } from '@central-design-system/components';
1834
+ import { computed, onMounted, ref, unref, watch } from 'vue';
1835
+
1836
+ const { plurals } = utils;
1837
+
1838
+ type TDataItem = {
1839
+ key: number;
1840
+ id: number;
1841
+ name: string;
1842
+ best_price: number | string;
1843
+ qty: number;
1844
+ price: number | string;
1845
+ tax: number;
1846
+ delay: number;
1847
+ download: boolean;
1848
+ };
1849
+
1850
+ const columns: NTable.NColumn.TColumns<TDataItem> = [
1851
+ { title: '№', dataIndex: 'id', width: 40 },
1852
+ { title: 'Позиция', dataIndex: 'name', width: 350 },
1853
+ { title: 'Лучшая цена с НДС, ₽', dataIndex: 'best_price', width: 200, align: 'right' },
1854
+ { title: 'Количество', dataIndex: 'qty', align: 'right', width: 80 },
1855
+ { title: 'Цена за единицу с НДС, ₽', dataIndex: 'price', align: 'right', width: 200 },
1856
+ { title: 'НДС, %', dataIndex: 'tax', align: 'right', width: 80 },
1857
+ { title: 'Срок поставки, дней', dataIndex: 'delay', align: 'right', width: 180 },
1858
+ { title: 'Документация', dataIndex: 'docs', align: 'right', width: 80 }
1859
+ ];
1860
+
1861
+ const data = ref<TDataItem[]>([]);
1862
+ const total = ref(10);
1863
+ const isLoading = ref(false);
1864
+ const tableDataSwitcher = ref(0);
1865
+
1866
+ const formatPrice = (price: number | string) =>
1867
+ new Intl.NumberFormat('ru-RU', {
1868
+ style: 'decimal',
1869
+ minimumFractionDigits: 2,
1870
+ maximumFractionDigits: 2
1871
+ }).format(+price);
1872
+
1873
+ const queryDataTableExample = async () => {
1874
+ try {
1875
+ isLoading.value = true;
1876
+
1877
+ const response = await fetch('https://cds-api.b2b-center.ru/api/v1/examples/data-table?example=example2');
1878
+ const { data: examples } = await response.json();
1879
+ // Принудительная задержка для примера загрузки
1880
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1881
+
1882
+ data.value =
1883
+ examples.map((item: TDataItem, idx: number) => ({
1884
+ ...item,
1885
+ best_price: formatPrice(item.best_price),
1886
+ price: formatPrice(item.price),
1887
+ key: idx
1888
+ })) ?? [];
1889
+ } catch (error) {
1890
+ console.error(error);
1891
+ } finally {
1892
+ isLoading.value = false;
1893
+ }
1894
+ };
1895
+
1896
+ const selectedRowKeys = ref<NTable.TKey[]>([]);
1897
+ const selectedRowText = computed(() => plurals(unref(selectedRowKeys).length, ['строка', 'строки', 'строк']));
1898
+
1899
+ const onSelectChange = (selectedKeys: NTable.TKey[]) => {
1900
+ selectedRowKeys.value = selectedKeys;
1901
+ };
1902
+
1903
+ const onTableChange: ITableProps<TDataItem>['onChange'] = (pagination) => {
1904
+ total.value = pagination.total!;
1905
+ };
1906
+
1907
+ onMounted(() => {
1908
+ queryDataTableExample();
1909
+ });
1910
+
1911
+ watch(tableDataSwitcher, () => {
1912
+ queryDataTableExample();
1913
+ });
1914
+ </script>
1915
+
1916
+ <template>
1917
+ <CdsTable
1918
+ class="min-w-[860px]"
1919
+ :columns="columns"
1920
+ :data="data"
1921
+ :pagination="{ total, position: ['bottomLeft'], counter: true, size: true }"
1922
+ :row-selection="{ selectedRowKeys, onChange: onSelectChange }"
1923
+ @change="onTableChange"
1924
+ >
1925
+ <template #header>
1926
+ <div class="flex justify-between">
1927
+ <div>
1928
+ <h4 class="mb-4">Позиции и цены</h4>
1929
+ <CdsTabs v-model="tableDataSwitcher">
1930
+ <CdsTab>Заявки на позиции</CdsTab>
1931
+ <CdsTab>Позиции без заявки</CdsTab>
1932
+ </CdsTabs>
1933
+ </div>
1934
+ <div>
1935
+ <CdsButton prepend-icon="upload" appearance="secondary" text="Загрузить из файла" />
1936
+ </div>
1937
+ </div>
1938
+ </template>
1939
+
1940
+ <template #toolbar>
1941
+ <div class="flex items-end justify-between">
1942
+ <div>
1943
+ <p>
1944
+ Выбрано: <strong>{{ selectedRowKeys.length }}</strong> {{ selectedRowText }}
1945
+ </p>
1946
+ </div>
1947
+ <div class="flex gap-2">
1948
+ <CdsTextInput prefix="на" suffix="%" model-value="5" hide-messages class="mr-2 w-[127px]" />
1949
+ <CdsButton appearance="secondary" text="Пересчитать" />
1950
+ <CdsButton text="Сохранить цены" />
1951
+ </div>
1952
+ </div>
1953
+ </template>
1954
+
1955
+ <template #bodyCell="{ column, record, text }">
1956
+ <template v-if="isLoading">
1957
+ <CdsSkeletonLoader type="text@4" />
1958
+ </template>
1959
+ <template v-else>
1960
+ <div v-if="column.dataIndex === 'name'" class="flex flex-col">
1961
+ <p>{{ text }}</p>
1962
+ <span class="text-content-secondary">Количество: 100 шт</span>
1963
+ <span class="text-content-secondary">Максимальная цена: 150.00 ₽ с НДС 20%</span>
1964
+ </div>
1965
+ <div v-if="column.dataIndex === 'docs'">
1966
+ <CdsButton
1967
+ size="sm"
1968
+ :prepend-icon="record.download ? 'download' : 'upload'"
1969
+ :appearance="record.download ? 'tertiary' : 'secondary'"
1970
+ :text="record.download ? 'Скачать' : 'Добавить'"
1971
+ />
1972
+ </div>
1973
+ </template>
1974
+ </template>
1975
+
1976
+ <template #emptyText>
1977
+ <div class="flex h-[500px] items-center justify-center">
1978
+ <CdsLoader v-if="isLoading" fixed overlay />
1979
+ <CdsEmptyState
1980
+ title="Нет данных"
1981
+ description="Нажмите кнопку ниже чтобы добавить данные в таблицу"
1982
+ illustration="documents"
1983
+ >
1984
+ <template #actions>
1985
+ <CdsButton text="Добавить данные" @click="queryDataTableExample" />
1986
+ </template>
1987
+ </CdsEmptyState>
1988
+ </div>
1989
+ </template>
1990
+ </CdsTable>
1991
+ </template>
1992
+ ```
1993
+
1994
+ ### Item list
1995
+
1996
+ A nested table inside an expanded row and a toolbar with fields and a dropdown calendar — a recipe for documents with line items.
1997
+
1998
+ **Example (SFC):**
1999
+
2000
+ ```vue
2001
+ <script setup lang="ts">
2002
+ import type { ITableProps, NTable } from '@central-design-system/components';
2003
+ import {
2004
+ CdsButton,
2005
+ CdsDatePicker,
2006
+ CdsEmptyState,
2007
+ CdsFunctionalButton,
2008
+ CdsLoader,
2009
+ CdsMenu,
2010
+ CdsSkeletonLoader,
2011
+ CdsTab,
2012
+ CdsTable,
2013
+ CdsTabs,
2014
+ CdsTextInput,
2015
+ utils
2016
+ } from '@central-design-system/components';
2017
+ import { computed, onMounted, ref, unref, watch } from 'vue';
2018
+
2019
+ const { plurals } = utils;
2020
+
2021
+ type TInnerDataItem = {
2022
+ key: number | string;
2023
+ factory: string;
2024
+ firm: number;
2025
+ place: string;
2026
+ date: string;
2027
+ price_one: number | string;
2028
+ qty: number;
2029
+ price: number | string;
2030
+ };
2031
+ type TDataItem = {
2032
+ key: number | string;
2033
+ id: number;
2034
+ name: string;
2035
+ group: number | string;
2036
+ size: number;
2037
+ qty: number | string;
2038
+ price: number | string;
2039
+ innerData: TInnerDataItem[];
2040
+ };
2041
+
2042
+ const columns: NTable.NColumn.TColumns<TDataItem> = [
2043
+ { title: '№', dataIndex: 'id', width: 59 },
2044
+ { title: 'Код ОЗМ', dataIndex: 'code', width: 91 },
2045
+ { title: 'Позиция', dataIndex: 'name', width: 545 },
2046
+ { title: 'Группа закупок', dataIndex: 'group', width: 187 },
2047
+ { title: 'Единица', dataIndex: 'size', width: 89 },
2048
+ { title: 'Количество', dataIndex: 'qty', align: 'right', width: 107 },
2049
+ { title: 'Плановая стоимость, ₽', dataIndex: 'price', align: 'right', width: 187 },
2050
+ { title: '', dataIndex: 'action', width: 45 }
2051
+ ];
2052
+ const innerColumns: NTable.NColumn.TColumns<TInnerDataItem> = [
2053
+ { title: 'Завод', dataIndex: 'factory', width: 98 },
2054
+ { title: 'Склад', dataIndex: 'firm', width: 92 },
2055
+ { title: 'Место поставки', dataIndex: 'place', width: 218 },
2056
+ { title: 'Плановая дата поставки', dataIndex: 'date', width: 280 },
2057
+ { title: 'Цена за единицу, ₽', dataIndex: 'price_one', align: 'right', width: 290 },
2058
+ { title: 'Количество', dataIndex: 'qty', align: 'right', width: 107 },
2059
+ { title: 'Плановая стоимость, ₽', dataIndex: 'price', align: 'right', width: 185 },
2060
+ { title: '', dataIndex: 'action', width: 44 }
2061
+ ];
2062
+
2063
+ const data = ref<TDataItem[]>([]);
2064
+ const total = ref(10);
2065
+ const isLoading = ref(false);
2066
+ const tableDataSwitcher = ref(0);
2067
+ const date = ref('');
2068
+
2069
+ const formatPrice = (price: number | string) =>
2070
+ new Intl.NumberFormat('ru-RU', {
2071
+ style: 'decimal',
2072
+ minimumFractionDigits: 2,
2073
+ maximumFractionDigits: 2
2074
+ }).format(+price);
2075
+
2076
+ const queryDataTableExample = async () => {
2077
+ try {
2078
+ isLoading.value = true;
2079
+
2080
+ const response = await fetch('https://cds-api.b2b-center.ru/api/v1/examples/data-table?example=example3');
2081
+ const { data: examples } = await response.json();
2082
+ // Принудительная задержка для примера загрузки
2083
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2084
+
2085
+ data.value =
2086
+ examples.map((item: TDataItem, idx: number) => ({
2087
+ ...item,
2088
+ price: formatPrice(item.price),
2089
+ innerData: item.innerData.map((innerItem, innerIdx) => ({
2090
+ ...innerItem,
2091
+ price_one: formatPrice(innerItem.price_one),
2092
+ key: `${idx}.${innerIdx}`
2093
+ })),
2094
+ key: idx
2095
+ })) ?? [];
2096
+ } catch (error) {
2097
+ console.error(error);
2098
+ } finally {
2099
+ isLoading.value = false;
2100
+ }
2101
+ };
2102
+
2103
+ const selectedRowKeys = ref<NTable.TKey[]>([]);
2104
+ const selectedRowText = computed(() => plurals(unref(selectedRowKeys).length, ['строка', 'строки', 'строк']));
2105
+
2106
+ const onSelectChange = (selectedKeys: NTable.TKey[]) => {
2107
+ selectedRowKeys.value = selectedKeys;
2108
+ };
2109
+
2110
+ const onTableChange: ITableProps<TDataItem>['onChange'] = (pagination) => {
2111
+ total.value = pagination.total!;
2112
+ };
2113
+
2114
+ onMounted(() => {
2115
+ queryDataTableExample();
2116
+ });
2117
+
2118
+ watch(tableDataSwitcher, () => {
2119
+ queryDataTableExample();
2120
+ });
2121
+ </script>
2122
+
2123
+ <template>
2124
+ <CdsTable
2125
+ class="min-w-[860px]"
2126
+ :columns="columns"
2127
+ :data="data"
2128
+ :pagination="{ total, position: ['bottomLeft'], counter: true, size: true }"
2129
+ :row-selection="{ selectedRowKeys, onChange: onSelectChange }"
2130
+ @change="onTableChange"
2131
+ >
2132
+ <template #header>
2133
+ <div class="flex justify-between">
2134
+ <div>
2135
+ <h4 class="mb-4">Список позиций</h4>
2136
+ <CdsTabs v-model="tableDataSwitcher">
2137
+ <CdsTab text="Позиции" />
2138
+ <CdsTab text="Адреса" />
2139
+ </CdsTabs>
2140
+ </div>
2141
+ <div class="flex gap-2">
2142
+ <CdsButton prepend-icon="download" appearance="secondary" text="Скачать шаблон" />
2143
+ <CdsButton prepend-icon="upload" appearance="secondary" text="Загрузить из файла" />
2144
+ </div>
2145
+ </div>
2146
+ </template>
2147
+
2148
+ <template #toolbar>
2149
+ <div class="flex flex-wrap items-end justify-between gap-4">
2150
+ <div class="flex items-end gap-4">
2151
+ <p class="min-w-[121px]">
2152
+ Выбрано: <strong>{{ selectedRowKeys.length }}</strong> {{ selectedRowText }}
2153
+ </p>
2154
+ <CdsTextInput label="Все адреса" placeholder="Введите адрес" hide-messages class="w-[250px]" />
2155
+ <CdsTextInput
2156
+ v-model="date"
2157
+ append-inner-icon="calendar"
2158
+ label="Дата"
2159
+ placeholder="Выберите дату"
2160
+ hide-messages
2161
+ class="w-[250px]"
2162
+ >
2163
+ <CdsMenu activator="parent" :close-on-content-click="false">
2164
+ <CdsDatePicker v-model="date" />
2165
+ </CdsMenu>
2166
+ </CdsTextInput>
2167
+ </div>
2168
+ <div class="flex gap-2">
2169
+ <CdsButton appearance="secondary" prepend-icon="add" text="Добавить позицию" />
2170
+ <CdsButton appearance="secondary" prepend-icon="add" text="Добавить адреса и позиции" />
2171
+ </div>
2172
+ </div>
2173
+ </template>
2174
+
2175
+ <template #bodyCell="{ column }">
2176
+ <template v-if="isLoading">
2177
+ <CdsSkeletonLoader type="text" />
2178
+ </template>
2179
+ <template v-else>
2180
+ <CdsFunctionalButton v-if="column.dataIndex === 'action'" icon="delete" />
2181
+ </template>
2182
+ </template>
2183
+
2184
+ <template #expandedRowRender="{ record }">
2185
+ <CdsTable :columns="innerColumns" :data="record.innerData" :pagination="false">
2186
+ <template #bodyCell="{ column }">
2187
+ <template v-if="column.dataIndex === 'action'">
2188
+ <CdsFunctionalButton icon="delete" />
2189
+ </template>
2190
+ </template>
2191
+ </CdsTable>
2192
+ </template>
2193
+
2194
+ <template #emptyText>
2195
+ <div class="flex h-[500px] items-center justify-center">
2196
+ <CdsLoader v-if="isLoading" fixed overlay />
2197
+ <CdsEmptyState
2198
+ title="Нет данных"
2199
+ description="Нажмите кнопку ниже чтобы добавить данные в таблицу"
2200
+ illustration="documents"
2201
+ >
2202
+ <template #actions>
2203
+ <CdsButton text="Добавить данные" @click="queryDataTableExample" />
2204
+ </template>
2205
+ </CdsEmptyState>
2206
+ </div>
2207
+ </template>
2208
+ </CdsTable>
2209
+ </template>
2210
+ ```
2211
+
2212
+ ## Выбор строк
2213
+
2214
+ Проп `row-selection` добавляет колонку с чекбоксами: выбранные ключи
2215
+ строк управляются снаружи, а меню выбора расширяется своими пунктами.
2216
+
2217
+ ### Selection and operations
2218
+
2219
+ The `row-selection` object turns on row selection: `selectedRowKeys` holds the selected keys, `onChange` reports the changes. Actions on the selected set fit nicely in the `toolbar` slot.
2220
+
2221
+ **Example (SFC):**
2222
+
2223
+ ```vue
2224
+ <script setup lang="ts">
2225
+ import type { NTable } from '@central-design-system/components';
2226
+ import { CdsButton, CdsTable } from '@central-design-system/components';
2227
+ import { reactive, toRefs } from 'vue';
2228
+
2229
+ const state = reactive<{ selectedRowKeys: NTable.TKey[]; loading: boolean }>({
2230
+ selectedRowKeys: [],
2231
+ loading: false
2232
+ });
2233
+
2234
+ const start = () => {
2235
+ state.loading = true;
2236
+
2237
+ setTimeout(() => {
2238
+ state.loading = false;
2239
+ state.selectedRowKeys = [];
2240
+ }, 1000);
2241
+ };
2242
+
2243
+ const cancel = () => {
2244
+ state.selectedRowKeys = [];
2245
+ };
2246
+
2247
+ const onSelectChange = (selectedRowKeys: NTable.TKey[]) => {
2248
+ state.selectedRowKeys = selectedRowKeys;
2249
+ };
2250
+
2251
+ const columns: NTable.NColumn.TColumns = [
2252
+ { title: 'Заголовок 1', dataIndex: 'name' },
2253
+ { title: 'Заголовок 2', dataIndex: 'age' },
2254
+ { title: 'Заголовок 3', dataIndex: 'address' },
2255
+ { title: 'Заголовок 4', dataIndex: 'phone' }
2256
+ ];
2257
+ const data = [...Array(8)].map((_, i) => ({
2258
+ key: i,
2259
+ name: `Текст ячейки ${i}`,
2260
+ age: `Текст ячейки ${i}`,
2261
+ address: `Текст ячейки ${i}`,
2262
+ phone: `Текст ячейки ${i}`
2263
+ }));
2264
+
2265
+ const { selectedRowKeys, loading } = toRefs(state);
2266
+ </script>
2267
+
2268
+ <template>
2269
+ <CdsTable
2270
+ :columns="columns"
2271
+ :data="data"
2272
+ :row-selection="{ selectedRowKeys, onChange: onSelectChange }"
2273
+ :pagination="false"
2274
+ >
2275
+ <template #toolbar>
2276
+ <div class="flex items-center justify-between">
2277
+ <p>
2278
+ Выбрано: <strong>{{ selectedRowKeys.length }}</strong> строк
2279
+ </p>
2280
+ <div class="flex gap-2">
2281
+ <CdsButton appearance="secondary" @click="cancel">Отменить</CdsButton>
2282
+ <CdsButton :loading="loading" @click="start">Загрузить</CdsButton>
2283
+ </div>
2284
+ </div>
2285
+ </template>
2286
+ </CdsTable>
2287
+ </template>
2288
+ ```
2289
+
2290
+ ### Custom selection menu items
2291
+
2292
+ The `selections` property fills the menu in the selection column header: the ready-made `SELECTION_ALL`, `SELECTION_INVERT` and `SELECTION_NONE` constants plus your own items with arbitrary logic.
2293
+
2294
+ **Example (SFC):**
2295
+
2296
+ ```vue
2297
+ <script setup lang="ts">
2298
+ import type { NTable } from '@central-design-system/components';
2299
+ import { CdsTable, SELECTION_ALL, SELECTION_INVERT, SELECTION_NONE } from '@central-design-system/components';
2300
+ import { computed, ref, unref } from 'vue';
2301
+
2302
+ const selectedRowKeys = ref<NTable.TKey[]>([]);
2303
+
2304
+ const onSelectChange = (selectedRows: NTable.TKey[]) => {
2305
+ selectedRowKeys.value = selectedRows;
2306
+ };
2307
+
2308
+ const rowSelection = computed(() => ({
2309
+ selectedRowKeys: unref(selectedRowKeys),
2310
+ onChange: onSelectChange,
2311
+ hideDefaultSelections: true,
2312
+ selections: [
2313
+ SELECTION_ALL,
2314
+ SELECTION_INVERT,
2315
+ SELECTION_NONE,
2316
+ {
2317
+ key: 'odd',
2318
+ text: 'Выбрать нечетные',
2319
+ onSelect: (selectedRows: NTable.TKey[]) => {
2320
+ selectedRowKeys.value = selectedRows.filter((_key, index) => index % 2 === 0);
2321
+ }
2322
+ },
2323
+ {
2324
+ key: 'even',
2325
+ text: 'Выбрать четные',
2326
+ onSelect: (selectedRows: NTable.TKey[]) => {
2327
+ selectedRowKeys.value = selectedRows.filter((_key, index) => index % 2 !== 0);
2328
+ }
2329
+ }
2330
+ ]
2331
+ }));
2332
+
2333
+ const columns: NTable.NColumn.TColumns = [
2334
+ { title: 'Заголовок 1', dataIndex: 'name' },
2335
+ { title: 'Заголовок 2', dataIndex: 'age' },
2336
+ { title: 'Заголовок 3', dataIndex: 'address' },
2337
+ { title: 'Заголовок 4', dataIndex: 'phone' }
2338
+ ];
2339
+ const data = [...Array(6)].map((_, i) => ({
2340
+ key: i,
2341
+ name: `Текст ячейки ${i}`,
2342
+ age: `Текст ячейки ${i}`,
2343
+ address: `Текст ячейки ${i}`,
2344
+ phone: `Текст ячейки ${i}`
2345
+ }));
2346
+ </script>
2347
+
2348
+ <template>
2349
+ <CdsTable :columns="columns" :data="data" :row-selection="rowSelection" :pagination="false" />
2350
+ </template>
2351
+ ```
2352
+
2353
+ ## Обзор
2354
+
2355
+ Таблица выводит большие наборы записей в колонках и берёт на себя работу
2356
+ с данными: сортировку, фильтры, пагинацию, выбор и раскрытие строк,
2357
+ фиксацию шапки и колонок. Колонки описываются пропом `columns`,
2358
+ данные — пропом `data`.
2359
+
2360
+ ### Showing data
2361
+
2362
+ A minimal table: an array of column descriptions in `columns`, the records in `data`. The row key comes from the record's `key` field or from the `row-key` prop.
2363
+
2364
+ **Example (SFC):**
2365
+
2366
+ ```vue
2367
+ <script setup lang="ts">
2368
+ import type { NTable } from '@central-design-system/components';
2369
+ import { CdsTable } from '@central-design-system/components';
2370
+
2371
+ type TSimpleRow = {
2372
+ key: string;
2373
+ name: string;
2374
+ age: string;
2375
+ address: string;
2376
+ phone: string;
2377
+ };
2378
+
2379
+ const simpleColumns: NTable.NColumn.TColumns<TSimpleRow> = [
2380
+ { title: 'Заголовок', dataIndex: 'name' },
2381
+ { title: 'Заголовок', dataIndex: 'age' },
2382
+ { title: 'Заголовок', dataIndex: 'address' },
2383
+ { title: 'Заголовок', dataIndex: 'phone' }
2384
+ ];
2385
+
2386
+ const makeSimpleData = (count: number): TSimpleRow[] =>
2387
+ [...Array(count)].map((_, i) => ({
2388
+ key: i.toString(),
2389
+ name: 'Текст ячейки',
2390
+ age: 'Текст ячейки',
2391
+ address: 'Текст ячейки',
2392
+ phone: 'Текст ячейки'
2393
+ }));
2394
+
2395
+ const columns = simpleColumns;
2396
+ const data = makeSimpleData(5);
2397
+ </script>
2398
+
2399
+ <template>
2400
+ <CdsTable :columns="columns" :data="data" />
2401
+ </template>
2402
+ ```
2403
+
2404
+ ### Showing data
2405
+
2406
+ The column's `align` property aligns the content — numeric values are conventionally aligned to the right.
2407
+
2408
+ **Example (SFC):**
2409
+
2410
+ ```vue
2411
+ <script setup lang="ts">
2412
+ import type { NTable } from '@central-design-system/components';
2413
+ import { CdsTable } from '@central-design-system/components';
2414
+
2415
+ type TDataType = { key: string; name: string; price: string; address: string; tags: string };
2416
+
2417
+ const columns: NTable.NColumn.TColumns<TDataType> = [
2418
+ { title: 'Наименование', dataIndex: 'name', key: 'name' },
2419
+ { title: 'Цена', dataIndex: 'price', key: 'price', align: 'right' },
2420
+ { title: 'Адрес', dataIndex: 'address', key: 'address' },
2421
+ { title: 'Наличие', key: 'tags', dataIndex: 'tags' }
2422
+ ];
2423
+ const data: TDataType[] = [
2424
+ { key: '1', name: 'iPhone 17 Pro Max', price: '$1500', address: 'г. Москва', tags: 'В наличии' },
2425
+ { key: '2', name: 'MacBook Air M5', price: '$2000', address: 'г. Москва', tags: 'В наличии' },
2426
+ { key: '3', name: 'Apple Vision Pro', price: '$3500', address: 'г. Нью-Йорк', tags: 'Нет в наличии' }
2427
+ ];
2428
+ </script>
2429
+
2430
+ <template>
2431
+ <CdsTable :columns="columns" :data="data" :pagination="false" />
2432
+ </template>
2433
+ ```
2434
+
2435
+ ### Sizes
2436
+
2437
+ The `size` prop controls the density: `md` is the default, `sm` and `xs` are for data-dense screens.
2438
+
2439
+ **Example (SFC):**
2440
+
2441
+ ```vue
2442
+ <script setup lang="ts">
2443
+ import type { NTable } from '@central-design-system/components';
2444
+ import { CdsTable } from '@central-design-system/components';
2445
+
2446
+ const columns: NTable.NColumn.TColumns = [...Array(4)].map((_, i) => ({
2447
+ title: 'Заголовок',
2448
+ dataIndex: `header${i + 1}`,
2449
+ key: (i + 1).toString()
2450
+ }));
2451
+ const data = [...Array(4)].map((_, i) => ({
2452
+ key: (i + 1).toString(),
2453
+ header1: 'Текст ячейки',
2454
+ header2: 'Текст ячейки',
2455
+ header3: 'Текст ячейки',
2456
+ header4: 'Текст ячейки'
2457
+ }));
2458
+ </script>
2459
+
2460
+ <template>
2461
+ <div class="flex flex-col gap-5">
2462
+ <CdsTable :columns="columns" :data="data" size="md" :pagination="false">
2463
+ <template #header>
2464
+ <h4>Medium size</h4>
2465
+ </template>
2466
+ </CdsTable>
2467
+
2468
+ <CdsTable :columns="columns" :data="data" size="sm" :pagination="false">
2469
+ <template #header>
2470
+ <h4>Small size</h4>
2471
+ </template>
2472
+ </CdsTable>
2473
+
2474
+ <CdsTable :columns="columns" :data="data" size="xs" :pagination="false">
2475
+ <template #header>
2476
+ <h4>Extra small size</h4>
2477
+ </template>
2478
+ </CdsTable>
2479
+ </div>
2480
+ </template>
2481
+ ```
2482
+
2483
+ ### Rows
2484
+
2485
+ The `row-class-name` property computes a row class from the record: the built-in `cds-table--striped` class alternates the background, `cds-table__row--warning` and `cds-table__row--error` add a status highlight. The `hovered` prop highlights the row under the cursor.
2486
+
2487
+ **Example (SFC):**
2488
+
2489
+ ```vue
2490
+ <script setup lang="ts">
2491
+ import type { NTable } from '@central-design-system/components';
2492
+ import { CdsTable } from '@central-design-system/components';
2493
+
2494
+ type TSimpleRow = {
2495
+ key: string;
2496
+ name: string;
2497
+ age: string;
2498
+ address: string;
2499
+ phone: string;
2500
+ };
2501
+
2502
+ const simpleColumns: NTable.NColumn.TColumns<TSimpleRow> = [
2503
+ { title: 'Заголовок', dataIndex: 'name' },
2504
+ { title: 'Заголовок', dataIndex: 'age' },
2505
+ { title: 'Заголовок', dataIndex: 'address' },
2506
+ { title: 'Заголовок', dataIndex: 'phone' }
2507
+ ];
2508
+
2509
+ const makeSimpleData = (count: number): TSimpleRow[] =>
2510
+ [...Array(count)].map((_, i) => ({
2511
+ key: i.toString(),
2512
+ name: 'Текст ячейки',
2513
+ age: 'Текст ячейки',
2514
+ address: 'Текст ячейки',
2515
+ phone: 'Текст ячейки'
2516
+ }));
2517
+
2518
+ const columns = simpleColumns;
2519
+ const data = makeSimpleData(4);
2520
+ </script>
2521
+
2522
+ <template>
2523
+ <CdsTable
2524
+ :columns="columns"
2525
+ :data="data"
2526
+ :row-class-name="(_record: unknown, index: number) => (index % 2 === 1 ? 'cds-table--striped' : '')"
2527
+ :pagination="false"
2528
+ hovered
2529
+ />
2530
+ </template>
2531
+ ```
2532
+
2533
+ ### Rows
2534
+
2535
+ Статусная подсветка строк классами `cds-table__row--warning` и
2536
+ `cds-table__row--error` через `row-class-name`.
2537
+
2538
+ **Example (SFC):**
2539
+
2540
+ ```vue
2541
+ <script setup lang="ts">
2542
+ import type { NTable } from '@central-design-system/components';
2543
+ import { CdsTable } from '@central-design-system/components';
2544
+
2545
+ type TSimpleRow = {
2546
+ key: string;
2547
+ name: string;
2548
+ age: string;
2549
+ address: string;
2550
+ phone: string;
2551
+ };
2552
+
2553
+ const simpleColumns: NTable.NColumn.TColumns<TSimpleRow> = [
2554
+ { title: 'Заголовок', dataIndex: 'name' },
2555
+ { title: 'Заголовок', dataIndex: 'age' },
2556
+ { title: 'Заголовок', dataIndex: 'address' },
2557
+ { title: 'Заголовок', dataIndex: 'phone' }
2558
+ ];
2559
+
2560
+ const makeSimpleData = (count: number): TSimpleRow[] =>
2561
+ [...Array(count)].map((_, i) => ({
2562
+ key: i.toString(),
2563
+ name: 'Текст ячейки',
2564
+ age: 'Текст ячейки',
2565
+ address: 'Текст ячейки',
2566
+ phone: 'Текст ячейки'
2567
+ }));
2568
+
2569
+ const columns = simpleColumns;
2570
+ const data = makeSimpleData(4);
2571
+ </script>
2572
+
2573
+ <template>
2574
+ <CdsTable
2575
+ :columns="columns"
2576
+ :data="data"
2577
+ :row-class-name="(_record, index) => (index % 2 === 1 ? 'cds-table__row--warning' : 'cds-table__row--error')"
2578
+ :pagination="false"
2579
+ hovered
2580
+ />
2581
+ </template>
2582
+ ```
2583
+
2584
+ ### Cell content
2585
+
2586
+ The column's `ellipsis` property truncates long text to a single line with an ellipsis — in cells and in headers alike.
2587
+
2588
+ **Example (SFC):**
2589
+
2590
+ ```vue
2591
+ <script setup lang="ts">
2592
+ import type { NTable } from '@central-design-system/components';
2593
+ import { CdsTable } from '@central-design-system/components';
2594
+
2595
+ type TDataType = { key: string; name: string; address: string };
2596
+
2597
+ const columns: NTable.NColumn.TColumns<TDataType> = [
2598
+ { title: 'Заголовок таблицы', dataIndex: 'name', key: 'name' },
2599
+ { title: 'Заголовок таблицы', dataIndex: 'address', key: 'address 1', ellipsis: true },
2600
+ { title: 'Заголовок таблицы Заголовок таблицы', dataIndex: 'address', key: 'address 2', ellipsis: true },
2601
+ { title: 'Заголовок таблицы Заголовок таблицы', dataIndex: 'address', key: 'address 3', ellipsis: true },
2602
+ { title: 'Заголовок таблицы Заголовок таблицы', dataIndex: 'address', key: 'address 4', ellipsis: true }
2603
+ ];
2604
+ const data: TDataType[] = [...Array(3)].map((_, i) => ({
2605
+ key: (i + 1).toString(),
2606
+ name: 'Текст ячейки',
2607
+ address: 'Текст ячейки Текст ячейки Текст ячейки Текст ячейки'
2608
+ }));
2609
+ </script>
2610
+
2611
+ <template>
2612
+ <CdsTable :columns="columns" :data="data" :pagination="false" />
2613
+ </template>
2614
+ ```
2615
+
2616
+ ### Responsiveness
2617
+
2618
+ The column's `responsive` property lists the breakpoints at which the column is visible — secondary columns get hidden on narrow screens.
2619
+
2620
+ **Example (SFC):**
2621
+
2622
+ ```vue
2623
+ <script setup lang="ts">
2624
+ import type { NTable } from '@central-design-system/components';
2625
+ import { CdsTable } from '@central-design-system/components';
2626
+
2627
+ const columns: NTable.NColumn.TColumns = [
2628
+ { title: 'Заголовок (all screens)', dataIndex: 'name', key: 'name' },
2629
+ { title: 'Заголовок (small screen or bigger)', dataIndex: 'age', key: 'age', responsive: ['smUp'] },
2630
+ { title: 'Заголовок (medium screen or bigger)', dataIndex: 'address', key: 'address', responsive: ['mdUp'] },
2631
+ {
2632
+ title: 'Заголовок (only md, sm, xs screen)',
2633
+ dataIndex: 'phone',
2634
+ key: 'phone',
2635
+ responsive: ['md', 'sm', 'xs']
2636
+ }
2637
+ ];
2638
+ const data = [{ key: '1', name: 'Текст ячейки', age: 'Текст ячейки', address: 'Текст ячейки', phone: 'Текст ячейки' }];
2639
+ </script>
2640
+
2641
+ <template>
2642
+ <CdsTable :columns="columns" :data="data" :pagination="false" />
2643
+ </template>
2644
+ ```
2645
+
2646
+ ## Структура и заголовки
2647
+
2648
+ Управление структурой таблицы: группировка шапки, объединение ячеек,
2649
+ слоты вокруг таблицы, изменение ширины колонок и декларативное описание
2650
+ колонок компонентами.
2651
+
2652
+ ### Grouping headers
2653
+
2654
+ A nested `children` array on a column joins several columns under a shared header — the header becomes multi-level.
2655
+
2656
+ **Example (SFC):**
2657
+
2658
+ ```vue
2659
+ <script setup lang="ts">
2660
+ import type { NTable } from '@central-design-system/components';
2661
+ import { CdsTable } from '@central-design-system/components';
2662
+
2663
+ const columns: NTable.NColumn.TColumns = [
2664
+ { title: 'Имя', dataIndex: 'name', key: 'name', width: 100, fixed: 'left' },
2665
+ {
2666
+ title: 'Информация',
2667
+ children: [
2668
+ { title: 'Возраст', dataIndex: 'age', key: 'age', width: 200 },
2669
+ {
2670
+ title: 'Адрес',
2671
+ children: [
2672
+ { title: 'Улица', dataIndex: 'street', key: 'street', width: 200 },
2673
+ {
2674
+ title: 'Дом',
2675
+ children: [
2676
+ { title: 'Корпус', dataIndex: 'building', key: 'building', width: 100 },
2677
+ { title: 'Квартира', dataIndex: 'number', key: 'number', width: 100 }
2678
+ ]
2679
+ }
2680
+ ]
2681
+ }
2682
+ ]
2683
+ },
2684
+ {
2685
+ title: 'Компания',
2686
+ children: [
2687
+ { title: 'Адрес компании', dataIndex: 'companyAddress', key: 'companyAddress', width: 200 },
2688
+ { title: 'Название компании', dataIndex: 'companyName', key: 'companyName' }
2689
+ ]
2690
+ },
2691
+ { title: 'Пол', dataIndex: 'gender', key: 'gender', width: 80, fixed: 'right' }
2692
+ ];
2693
+
2694
+ const data = [...Array(10)].map((_, i) => ({
2695
+ key: i,
2696
+ name: 'Иван',
2697
+ age: i + 1,
2698
+ street: 'Главная',
2699
+ building: 'Б',
2700
+ number: 1004,
2701
+ companyAddress: 'г. Москва, ул. Ленина',
2702
+ companyName: 'B2B-Center',
2703
+ gender: 'Муж'
2704
+ }));
2705
+ </script>
2706
+
2707
+ <template>
2708
+ <CdsTable :columns="columns" :data="data" :pagination="false" :scroll="{ x: 'calc(700px + 50%)', y: 240 }" bordered />
2709
+ </template>
2710
+ ```
2711
+
2712
+ ### Merging cells
2713
+
2714
+ The column's `customCell` function returns `colSpan`/`rowSpan` for a specific cell, while `colSpan` on the column itself merges header cells. A cell with `colSpan: 0` is not rendered.
2715
+
2716
+ **Example (SFC):**
2717
+
2718
+ ```vue
2719
+ <script setup lang="ts">
2720
+ import type { NTable } from '@central-design-system/components';
2721
+ import { CdsTable } from '@central-design-system/components';
2722
+
2723
+ type TDataType = { key: string; name: string; age: number; tel: string; phone: string; address: string };
2724
+
2725
+ const columns: NTable.NColumn.TColumns<TDataType> = [
2726
+ {
2727
+ title: 'Имя',
2728
+ dataIndex: 'name',
2729
+ customCell: (_, index) => ({ colSpan: index! < 4 ? 1 : 5 })
2730
+ },
2731
+ {
2732
+ title: 'Возраст',
2733
+ dataIndex: 'age',
2734
+ customCell: (_, index) => {
2735
+ if (index === 4) return { colSpan: 0 };
2736
+ }
2737
+ },
2738
+ {
2739
+ title: 'Номер телефона',
2740
+ colSpan: 2,
2741
+ dataIndex: 'tel',
2742
+ customCell: (_, index) => {
2743
+ if (index === 2) return { rowSpan: 2 };
2744
+ if (index === 3) return { rowSpan: 0 };
2745
+ if (index === 4) return { colSpan: 0 };
2746
+ }
2747
+ },
2748
+ {
2749
+ title: 'Номер домашнего телефона',
2750
+ colSpan: 0,
2751
+ dataIndex: 'phone',
2752
+ customCell: (_, index) => {
2753
+ if (index === 4) return { colSpan: 0 };
2754
+ }
2755
+ },
2756
+ {
2757
+ title: 'Адрес',
2758
+ dataIndex: 'address',
2759
+ customCell: (_, index) => {
2760
+ if (index === 4) return { colSpan: 0 };
2761
+ }
2762
+ }
2763
+ ];
2764
+ const data: TDataType[] = [
2765
+ {
2766
+ key: '1',
2767
+ name: 'Иванов Иван',
2768
+ age: 20,
2769
+ tel: '79999999999',
2770
+ phone: '74959999999',
2771
+ address: 'г. Москва, ул. Ленина д. 1'
2772
+ },
2773
+ {
2774
+ key: '2',
2775
+ name: 'Иванов Василий',
2776
+ tel: '79999999999',
2777
+ phone: '74959999999',
2778
+ age: 32,
2779
+ address: 'г. Москва, ул. Ленина д. 2'
2780
+ },
2781
+ {
2782
+ key: '3',
2783
+ name: 'Иванов Кирилл',
2784
+ age: 41,
2785
+ tel: '79999999999',
2786
+ phone: '74959999999',
2787
+ address: 'г. Москва, ул. Ленина д. 3'
2788
+ },
2789
+ {
2790
+ key: '4',
2791
+ name: 'Иванов Александр',
2792
+ age: 30,
2793
+ tel: '79999999999',
2794
+ phone: '74959999999',
2795
+ address: 'г. Екатеринбург, ул. Ленина д. 4'
2796
+ },
2797
+ {
2798
+ key: '5',
2799
+ name: 'Иванов Иван',
2800
+ age: 18,
2801
+ tel: '79999999999',
2802
+ phone: '74959999999',
2803
+ address: 'г. Новосибирск, ул. Ленина д. 5'
2804
+ }
2805
+ ];
2806
+ </script>
2807
+
2808
+ <template>
2809
+ <CdsTable :columns="columns" :data="data" :pagination="false" />
2810
+ </template>
2811
+ ```
2812
+
2813
+ ### Header, toolbar and footer
2814
+
2815
+ The `header`, `toolbar` and `footer` slots add your own areas around the table, and the `pagination` prop adds built-in pagination with a record counter and a page size selector.
2816
+
2817
+ **Example (SFC):**
2818
+
2819
+ ```vue
2820
+ <script setup lang="ts">
2821
+ import type { ITableProps, NTable } from '@central-design-system/components';
2822
+ import { CdsButton, CdsTab, CdsTable, CdsTabs, CdsTextInput } from '@central-design-system/components';
2823
+ import { ref } from 'vue';
2824
+
2825
+ type TDataType = { key: number; name: string; money: string; address: string };
2826
+
2827
+ const columns: NTable.NColumn.TColumns<TDataType> = [
2828
+ { title: 'Название', dataIndex: 'name' },
2829
+ { title: 'Адрес', dataIndex: 'address' },
2830
+ { title: 'Цена', className: 'column-money', dataIndex: 'money', align: 'right' }
2831
+ ];
2832
+ const data: TDataType[] = [...Array(100)].map((_, i) => ({
2833
+ key: i,
2834
+ name: `Позиция ${i + 1}`,
2835
+ money: new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB' }).format((i + 1) * 10000),
2836
+ address: `г. Москва, ул. Ленина, д.${i}`
2837
+ }));
2838
+
2839
+ const contentSwitcher = ref(0);
2840
+ const total = ref(10);
2841
+
2842
+ const onChange: ITableProps<TDataType>['onChange'] = (pagination) => {
2843
+ total.value = pagination.total!;
2844
+ };
2845
+ </script>
2846
+
2847
+ <template>
2848
+ <CdsTable
2849
+ :columns="columns"
2850
+ :data="data"
2851
+ :pagination="{ total, counter: true, size: true, position: ['bottomLeft'] }"
2852
+ @change="onChange"
2853
+ >
2854
+ <template #header>
2855
+ <div class="flex justify-between">
2856
+ <div>
2857
+ <h4 class="mb-2">Заголовок таблицы</h4>
2858
+ <p class="mb-4 text-content-secondary">Описание таблицы в слоте заголовка таблицы</p>
2859
+ <div class="flex">
2860
+ <CdsTabs v-model="contentSwitcher">
2861
+ <CdsTab v-for="i in 5" :key="i">Текст</CdsTab>
2862
+ </CdsTabs>
2863
+ </div>
2864
+ </div>
2865
+ <div>
2866
+ <CdsButton appearance="secondary" text="Внешнее действия" />
2867
+ </div>
2868
+ </div>
2869
+ </template>
2870
+
2871
+ <template #toolbar>
2872
+ <div class="flex justify-between">
2873
+ <div class="flex gap-4">
2874
+ <CdsTextInput prepend-inner-icon="search" placeholder="Описание поиска" hide-messages class="w-[250px]" />
2875
+ <CdsButton prepend-icon="filters" text="Фильтры" appearance="tertiary" />
2876
+ </div>
2877
+ <div class="flex gap-2">
2878
+ <CdsButton appearance="secondary" text="Отменить" />
2879
+ <CdsButton text="Действие" />
2880
+ </div>
2881
+ </div>
2882
+ </template>
2883
+
2884
+ <template #footer>
2885
+ <div class="flex items-center justify-between">
2886
+ <h4>Подвал таблицы</h4>
2887
+ <div class="flex gap-2">
2888
+ <CdsButton appearance="secondary" text="Отменить" />
2889
+ <CdsButton text="Действие" />
2890
+ </div>
2891
+ </div>
2892
+ </template>
2893
+ </CdsTable>
2894
+ </template>
2895
+ ```
2896
+
2897
+ ### Resizing columns
2898
+
2899
+ The column's `resizable` property enables dragging the header border. The new width is reported by the `resize-column` event — store it in the column description.
2900
+
2901
+ **Example (SFC):**
2902
+
2903
+ ```vue
2904
+ <script setup lang="ts">
2905
+ import type { NTable } from '@central-design-system/components';
2906
+ import { CdsTable } from '@central-design-system/components';
2907
+ import { ref } from 'vue';
2908
+
2909
+ type TDataType = { key: string; name: string; price: string; address: string; tags: string };
2910
+
2911
+ const columns = ref<NTable.NColumn.TColumns<TDataType>>([
2912
+ { title: 'Наименование', dataIndex: 'name', key: 'name', resizable: true, width: 150 },
2913
+ {
2914
+ title: 'Цена',
2915
+ dataIndex: 'price',
2916
+ key: 'price',
2917
+ align: 'right',
2918
+ resizable: true,
2919
+ width: 100,
2920
+ minWidth: 100,
2921
+ maxWidth: 200
2922
+ },
2923
+ { title: 'Адрес', dataIndex: 'address', key: 'address' },
2924
+ { title: 'Наличие', key: 'tags', dataIndex: 'tags' }
2925
+ ]);
2926
+ const data: TDataType[] = [
2927
+ {
2928
+ key: '1',
2929
+ name: 'iPhone 17 Pro Max',
2930
+ price: '$1500',
2931
+ address: 'г. Москва, ул. Ленина, д. 1',
2932
+ tags: 'В наличии'
2933
+ },
2934
+ {
2935
+ key: '2',
2936
+ name: 'MacBook Air M5',
2937
+ price: '$2000',
2938
+ address: 'г. Москва, ул. Ленина, д. 1',
2939
+ tags: 'В наличии'
2940
+ },
2941
+ {
2942
+ key: '3',
2943
+ name: 'Apple Vision Pro',
2944
+ price: '$3500',
2945
+ address: 'г. Нью-Йорк, ул. Ленина, д. 1',
2946
+ tags: 'Нет в наличии'
2947
+ }
2948
+ ];
2949
+
2950
+ const handleResizeColumn = (width: number, column: NTable.NColumn.IColumn) => {
2951
+ column.width = width;
2952
+ };
2953
+ </script>
2954
+
2955
+ <template>
2956
+ <CdsTable :columns="columns" :data="data" :pagination="false" @resize-column="handleResizeColumn" />
2957
+ </template>
2958
+ ```
2959
+
2960
+ ### Declarative column descriptions
2961
+
2962
+ Instead of the `columns` array, columns can be described in the template with the `CdsTableColumn` and `CdsTableColumnGroup` components — handy when headers and cells need their own markup.
2963
+
2964
+ **Example (SFC):**
2965
+
2966
+ ```vue
2967
+ <script setup lang="ts">
2968
+ import { CdsStatus, CdsTable, CdsTableColumn, CdsTableColumnGroup, CdsToggle } from '@central-design-system/components';
2969
+
2970
+ type TDataType = {
2971
+ key: string;
2972
+ firstName: string;
2973
+ lastName: string;
2974
+ age: number;
2975
+ address: string;
2976
+ tags: string[];
2977
+ };
2978
+
2979
+ const data: TDataType[] = [
2980
+ {
2981
+ key: '1',
2982
+ firstName: 'Иван',
2983
+ lastName: 'Иванов',
2984
+ age: 20,
2985
+ address: 'г. Москва, ул. Ленина, д. 1',
2986
+ tags: ['Senior', 'Developer']
2987
+ },
2988
+ {
2989
+ key: '2',
2990
+ firstName: 'Анатолий',
2991
+ lastName: 'Анатольев',
2992
+ age: 42,
2993
+ address: 'г. Москва, ул. Ленина, д. 2',
2994
+ tags: ['Junior', 'Developer']
2995
+ },
2996
+ {
2997
+ key: '3',
2998
+ firstName: 'Игнат',
2999
+ lastName: 'Игнатьев',
3000
+ age: 33,
3001
+ address: 'г. Москва, ул. Ленина, д. 3',
3002
+ tags: ['Junior', 'Developer']
3003
+ },
3004
+ {
3005
+ key: '4',
3006
+ firstName: 'Петр',
3007
+ lastName: 'Петров',
3008
+ age: 28,
3009
+ address: 'г. Москва, ул. Ленина, д. 4',
3010
+ tags: ['Middle', 'Developer']
3011
+ }
3012
+ ];
3013
+ </script>
3014
+
3015
+ <template>
3016
+ <CdsTable :data="data" :pagination="false">
3017
+ <CdsTableColumnGroup>
3018
+ <template #title>
3019
+ <span class="text-brand-primary">Сотрудник</span>
3020
+ </template>
3021
+ <CdsTableColumn key="firstName" data-index="firstName">
3022
+ <template #title>
3023
+ <span class="text-brand-primary">Имя</span>
3024
+ </template>
3025
+ </CdsTableColumn>
3026
+ <CdsTableColumn key="lastName" title="Фамилия" data-index="lastName" />
3027
+ </CdsTableColumnGroup>
3028
+ <CdsTableColumn key="age" title="Возраст" data-index="age" />
3029
+ <CdsTableColumn key="address" title="Адрес" data-index="address" />
3030
+ <CdsTableColumn key="tags" title="Особенности" data-index="tags">
3031
+ <template #default="{ text: tags }">
3032
+ <CdsStatus
3033
+ v-for="tag in tags"
3034
+ :key="tag"
3035
+ :status="tag === 'Junior' ? 'danger' : tag === 'developer' ? 'info' : 'success'"
3036
+ class="mr-2"
3037
+ >
3038
+ {{ tag }}
3039
+ </CdsStatus>
3040
+ </template>
3041
+ </CdsTableColumn>
3042
+ <CdsTableColumn key="action" title="Действия" data-index="action">
3043
+ <template #default="{ record }">
3044
+ <CdsToggle :disabled="!record.tags.includes('Junior')" hide-messages />
3045
+ </template>
3046
+ </CdsTableColumn>
3047
+ </CdsTable>
3048
+ </template>
3049
+ ```