@krizaka/ui 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (498) hide show
  1. package/README.md +33 -8
  2. package/dist/audio-player.d.ts +113 -0
  3. package/dist/audio-player.js +207 -0
  4. package/dist/audio-player.js.map +1 -0
  5. package/dist/{button-Cgfkg_3y.d.ts → button-BMAO7dKF.d.ts} +29 -2
  6. package/dist/button.d.ts +32 -5
  7. package/dist/button.js +62 -9
  8. package/dist/button.js.map +1 -1
  9. package/dist/chip-link.d.ts +53 -0
  10. package/dist/chip-link.js +32 -0
  11. package/dist/chip-link.js.map +1 -0
  12. package/dist/chunk-2USC2YT6.js +17 -0
  13. package/dist/chunk-2USC2YT6.js.map +1 -0
  14. package/dist/chunk-43PTCDTL.js +31 -0
  15. package/dist/chunk-43PTCDTL.js.map +1 -0
  16. package/dist/chunk-6ISW63JI.js +11 -0
  17. package/dist/chunk-6ISW63JI.js.map +1 -0
  18. package/dist/chunk-EUPTXKH7.js +95 -0
  19. package/dist/chunk-EUPTXKH7.js.map +1 -0
  20. package/dist/chunk-HAQWKORZ.js +117 -0
  21. package/dist/chunk-HAQWKORZ.js.map +1 -0
  22. package/dist/{chunk-FHYZSO2S.js → chunk-R6SDS7JM.js} +5 -3
  23. package/dist/chunk-R6SDS7JM.js.map +1 -0
  24. package/dist/chunk-RUPNPMP3.js +103 -0
  25. package/dist/chunk-RUPNPMP3.js.map +1 -0
  26. package/dist/chunk-RUPRYJ45.js +82 -0
  27. package/dist/chunk-RUPRYJ45.js.map +1 -0
  28. package/dist/chunk-VDR2EP6K.js +49 -0
  29. package/dist/chunk-VDR2EP6K.js.map +1 -0
  30. package/dist/chunk-WEVYKZIF.js +33 -0
  31. package/dist/chunk-WEVYKZIF.js.map +1 -0
  32. package/dist/chunk-XUMJA5ZI.js +283 -0
  33. package/dist/chunk-XUMJA5ZI.js.map +1 -0
  34. package/dist/chunk-YH4LMWRU.js +92 -0
  35. package/dist/chunk-YH4LMWRU.js.map +1 -0
  36. package/dist/chunk-YX6CNVJA.js +94 -0
  37. package/dist/chunk-YX6CNVJA.js.map +1 -0
  38. package/dist/chunk-ZIXB33X7.js +95 -0
  39. package/dist/chunk-ZIXB33X7.js.map +1 -0
  40. package/dist/combobox.d.ts +188 -0
  41. package/dist/combobox.js +16 -0
  42. package/dist/combobox.js.map +1 -0
  43. package/dist/command.d.ts +7 -7
  44. package/dist/command.js +3 -2
  45. package/dist/command.js.map +1 -1
  46. package/dist/confirm-button.d.ts +1 -1
  47. package/dist/confirm-button.js +2 -1
  48. package/dist/confirm-button.js.map +1 -1
  49. package/dist/data-table.d.ts +246 -0
  50. package/dist/data-table.js +290 -0
  51. package/dist/data-table.js.map +1 -0
  52. package/dist/date-picker.d.ts +288 -0
  53. package/dist/date-picker.js +364 -0
  54. package/dist/date-picker.js.map +1 -0
  55. package/dist/{dialog-GQ4Nvuyf.d.ts → dialog-D42QwYb_.d.ts} +11 -1
  56. package/dist/dialog.d.ts +1 -1
  57. package/dist/dialog.js +3 -2
  58. package/dist/dialog.js.map +1 -1
  59. package/dist/file-upload.d.ts +238 -0
  60. package/dist/file-upload.js +437 -0
  61. package/dist/file-upload.js.map +1 -0
  62. package/dist/filter-bar.d.ts +139 -0
  63. package/dist/filter-bar.js +155 -0
  64. package/dist/filter-bar.js.map +1 -0
  65. package/dist/glow-card.d.ts +20 -0
  66. package/dist/glow-card.js +30 -0
  67. package/dist/glow-card.js.map +1 -0
  68. package/dist/image-cropper.d.ts +115 -0
  69. package/dist/image-cropper.js +166 -0
  70. package/dist/image-cropper.js.map +1 -0
  71. package/dist/index.d.ts +2 -20
  72. package/dist/index.js +7 -64
  73. package/dist/index.js.map +1 -1
  74. package/dist/iso.d.ts +400 -0
  75. package/dist/iso.js +363 -0
  76. package/dist/iso.js.map +1 -0
  77. package/dist/loader.d.ts +206 -0
  78. package/dist/loader.js +138 -0
  79. package/dist/loader.js.map +1 -0
  80. package/dist/marquee.d.ts +33 -0
  81. package/dist/marquee.js +52 -0
  82. package/dist/marquee.js.map +1 -0
  83. package/dist/media-capture.d.ts +155 -0
  84. package/dist/media-capture.js +242 -0
  85. package/dist/media-capture.js.map +1 -0
  86. package/dist/meter.d.ts +146 -0
  87. package/dist/meter.js +107 -0
  88. package/dist/meter.js.map +1 -0
  89. package/dist/motion.css +394 -3
  90. package/dist/motion.d.ts +46 -0
  91. package/dist/motion.js +10 -0
  92. package/dist/motion.js.map +1 -0
  93. package/dist/native/media-capture.d.ts +49 -0
  94. package/dist/native/media-capture.js +113 -0
  95. package/dist/native/media-capture.js.map +1 -0
  96. package/dist/native/video-player.d.ts +50 -0
  97. package/dist/native/video-player.js +69 -0
  98. package/dist/native/video-player.js.map +1 -0
  99. package/dist/native.d.ts +66 -2
  100. package/dist/native.js +166 -68
  101. package/dist/native.js.map +1 -1
  102. package/dist/nav-bar.d.ts +178 -0
  103. package/dist/nav-bar.js +157 -0
  104. package/dist/nav-bar.js.map +1 -0
  105. package/dist/number-ticker.d.ts +32 -0
  106. package/dist/number-ticker.js +61 -0
  107. package/dist/number-ticker.js.map +1 -0
  108. package/dist/pagination.d.ts +141 -0
  109. package/dist/pagination.js +165 -0
  110. package/dist/pagination.js.map +1 -0
  111. package/dist/progress.d.ts +56 -1
  112. package/dist/progress.js +72 -25
  113. package/dist/progress.js.map +1 -1
  114. package/dist/search-field.d.ts +100 -0
  115. package/dist/search-field.js +167 -0
  116. package/dist/search-field.js.map +1 -0
  117. package/dist/skeleton.d.ts +18 -3
  118. package/dist/skeleton.js +9 -1
  119. package/dist/skeleton.js.map +1 -1
  120. package/dist/spinner.d.ts +21 -5
  121. package/dist/spinner.js +13 -12
  122. package/dist/spinner.js.map +1 -1
  123. package/dist/stepper.d.ts +112 -0
  124. package/dist/stepper.js +67 -0
  125. package/dist/stepper.js.map +1 -0
  126. package/dist/tabs.d.ts +1 -1
  127. package/dist/tabs.js +14 -8
  128. package/dist/tabs.js.map +1 -1
  129. package/dist/tag-input.d.ts +44 -0
  130. package/dist/tag-input.js +67 -0
  131. package/dist/tag-input.js.map +1 -0
  132. package/dist/theme.d.ts +1 -1
  133. package/dist/theme.js +2 -1
  134. package/dist/theme.js.map +1 -1
  135. package/dist/toast.d.ts +39 -4
  136. package/dist/toast.js +81 -7
  137. package/dist/toast.js.map +1 -1
  138. package/dist/video-player.d.ts +204 -0
  139. package/dist/video-player.js +371 -0
  140. package/dist/video-player.js.map +1 -0
  141. package/dist/video-trimmer.d.ts +142 -0
  142. package/dist/video-trimmer.js +193 -0
  143. package/dist/video-trimmer.js.map +1 -0
  144. package/package.json +375 -7
  145. package/registry/alert.json +1 -1
  146. package/registry/audio-player.json +282 -0
  147. package/registry/avatar.json +1 -1
  148. package/registry/badge.json +1 -1
  149. package/registry/button.json +232 -26
  150. package/registry/card.json +1 -1
  151. package/registry/checkbox.json +1 -1
  152. package/registry/chip-link.json +158 -0
  153. package/registry/chip.json +1 -1
  154. package/registry/combobox.json +475 -0
  155. package/registry/command.json +1 -1
  156. package/registry/confirm-button.json +91 -7
  157. package/registry/countdown.json +1 -1
  158. package/registry/data-table.json +500 -0
  159. package/registry/date-picker.json +978 -0
  160. package/registry/dialog.json +15 -7
  161. package/registry/dropdown-menu.json +1 -1
  162. package/registry/empty-state.json +1 -1
  163. package/registry/examples/audio-player/podcast.js +22 -0
  164. package/registry/examples/audio-player/podcast.tsx +19 -0
  165. package/registry/examples/audio-player/voice-note.d.ts +4 -0
  166. package/registry/examples/audio-player/voice-note.js +12 -0
  167. package/registry/examples/audio-player/voice-note.tsx +12 -0
  168. package/registry/examples/button/error.d.ts +4 -0
  169. package/registry/examples/button/error.js +11 -0
  170. package/registry/examples/button/error.tsx +10 -0
  171. package/registry/examples/button/loading.tsx +1 -1
  172. package/registry/examples/button/save-flow.d.ts +4 -0
  173. package/registry/examples/button/save-flow.js +28 -0
  174. package/registry/examples/button/save-flow.tsx +24 -0
  175. package/registry/examples/button/signature.d.ts +4 -0
  176. package/registry/examples/button/signature.js +17 -0
  177. package/registry/examples/button/signature.tsx +19 -0
  178. package/registry/examples/button/upload-progress.d.ts +4 -0
  179. package/registry/examples/button/upload-progress.js +23 -0
  180. package/registry/examples/button/upload-progress.tsx +26 -0
  181. package/registry/examples/chip-link/filters.d.ts +4 -0
  182. package/registry/examples/chip-link/filters.js +12 -0
  183. package/registry/examples/chip-link/filters.tsx +17 -0
  184. package/registry/examples/combobox/async.d.ts +4 -0
  185. package/registry/examples/combobox/async.js +23 -0
  186. package/registry/examples/combobox/async.tsx +22 -0
  187. package/registry/examples/combobox/creatable.d.ts +4 -0
  188. package/registry/examples/combobox/creatable.js +32 -0
  189. package/registry/examples/combobox/creatable.tsx +28 -0
  190. package/registry/examples/combobox/invalid.d.ts +4 -0
  191. package/registry/examples/combobox/invalid.js +23 -0
  192. package/registry/examples/combobox/invalid.tsx +17 -0
  193. package/registry/examples/combobox/multiple.d.ts +4 -0
  194. package/registry/examples/combobox/multiple.js +29 -0
  195. package/registry/examples/combobox/multiple.tsx +26 -0
  196. package/registry/examples/data-table/cards.d.ts +4 -0
  197. package/registry/examples/data-table/cards.js +21 -0
  198. package/registry/examples/data-table/cards.tsx +20 -0
  199. package/registry/examples/data-table/empty.d.ts +4 -0
  200. package/registry/examples/data-table/empty.js +26 -0
  201. package/registry/examples/data-table/empty.tsx +21 -0
  202. package/registry/examples/data-table/error.d.ts +4 -0
  203. package/registry/examples/data-table/error.js +28 -0
  204. package/registry/examples/data-table/error.tsx +29 -0
  205. package/registry/examples/data-table/jobs.d.ts +4 -0
  206. package/registry/examples/data-table/jobs.js +37 -0
  207. package/registry/examples/data-table/jobs.tsx +36 -0
  208. package/registry/examples/data-table/loading.d.ts +4 -0
  209. package/registry/examples/data-table/loading.js +16 -0
  210. package/registry/examples/data-table/loading.tsx +12 -0
  211. package/registry/examples/data-table/virtualized.d.ts +4 -0
  212. package/registry/examples/data-table/virtualized.js +21 -0
  213. package/registry/examples/data-table/virtualized.tsx +20 -0
  214. package/registry/examples/date-picker/calendar.d.ts +4 -0
  215. package/registry/examples/date-picker/calendar.js +14 -0
  216. package/registry/examples/date-picker/calendar.tsx +16 -0
  217. package/registry/examples/date-picker/french.d.ts +4 -0
  218. package/registry/examples/date-picker/french.js +20 -0
  219. package/registry/examples/date-picker/french.tsx +14 -0
  220. package/registry/examples/date-picker/open.d.ts +4 -0
  221. package/registry/examples/date-picker/open.js +11 -0
  222. package/registry/examples/date-picker/open.tsx +8 -0
  223. package/registry/examples/date-picker/range.d.ts +4 -0
  224. package/registry/examples/date-picker/range.js +29 -0
  225. package/registry/examples/date-picker/range.tsx +25 -0
  226. package/registry/examples/date-picker/schedule.d.ts +4 -0
  227. package/registry/examples/date-picker/schedule.js +26 -0
  228. package/registry/examples/date-picker/schedule.tsx +22 -0
  229. package/registry/examples/dialog/left.d.ts +4 -0
  230. package/registry/examples/dialog/left.js +22 -0
  231. package/registry/examples/dialog/left.tsx +32 -0
  232. package/registry/examples/file-upload/images.d.ts +4 -0
  233. package/registry/examples/file-upload/images.js +32 -0
  234. package/registry/examples/file-upload/images.tsx +25 -0
  235. package/registry/examples/file-upload/states.d.ts +4 -0
  236. package/registry/examples/file-upload/states.js +28 -0
  237. package/registry/examples/file-upload/states.tsx +23 -0
  238. package/registry/examples/file-upload/tus.d.ts +4 -0
  239. package/registry/examples/file-upload/tus.js +23 -0
  240. package/registry/examples/file-upload/tus.tsx +26 -0
  241. package/registry/examples/file-upload/video.d.ts +4 -0
  242. package/registry/examples/file-upload/video.js +39 -0
  243. package/registry/examples/file-upload/video.tsx +36 -0
  244. package/registry/examples/filter-bar/address.d.ts +4 -0
  245. package/registry/examples/filter-bar/address.js +29 -0
  246. package/registry/examples/filter-bar/address.tsx +27 -0
  247. package/registry/examples/filter-bar/jobs.d.ts +4 -0
  248. package/registry/examples/filter-bar/jobs.js +37 -0
  249. package/registry/examples/filter-bar/jobs.tsx +37 -0
  250. package/registry/examples/filter-bar/menu.d.ts +4 -0
  251. package/registry/examples/filter-bar/menu.js +27 -0
  252. package/registry/examples/filter-bar/menu.tsx +23 -0
  253. package/registry/examples/glow-card/link-card.d.ts +4 -0
  254. package/registry/examples/glow-card/link-card.js +14 -0
  255. package/registry/examples/glow-card/link-card.tsx +13 -0
  256. package/registry/examples/glow-card/plans.d.ts +4 -0
  257. package/registry/examples/glow-card/plans.js +25 -0
  258. package/registry/examples/glow-card/plans.tsx +26 -0
  259. package/registry/examples/glow-card/spotlight.d.ts +4 -0
  260. package/registry/examples/glow-card/spotlight.js +14 -0
  261. package/registry/examples/glow-card/spotlight.tsx +15 -0
  262. package/registry/examples/image-cropper/avatar.d.ts +4 -0
  263. package/registry/examples/image-cropper/avatar.js +13 -0
  264. package/registry/examples/image-cropper/avatar.tsx +14 -0
  265. package/registry/examples/image-cropper/banner.d.ts +4 -0
  266. package/registry/examples/image-cropper/banner.js +11 -0
  267. package/registry/examples/image-cropper/banner.tsx +10 -0
  268. package/registry/examples/image-cropper/vertical-video.d.ts +4 -0
  269. package/registry/examples/image-cropper/vertical-video.js +12 -0
  270. package/registry/examples/image-cropper/vertical-video.tsx +16 -0
  271. package/registry/examples/iso/compose.d.ts +4 -0
  272. package/registry/examples/iso/compose.js +22 -0
  273. package/registry/examples/iso/compose.tsx +19 -0
  274. package/registry/examples/iso/orazaka-hero.d.ts +4 -0
  275. package/registry/examples/iso/orazaka-hero.js +26 -0
  276. package/registry/examples/iso/orazaka-hero.tsx +21 -0
  277. package/registry/examples/iso/orochia-hero.d.ts +4 -0
  278. package/registry/examples/iso/orochia-hero.js +29 -0
  279. package/registry/examples/iso/orochia-hero.tsx +23 -0
  280. package/registry/examples/iso/still.d.ts +4 -0
  281. package/registry/examples/iso/still.js +21 -0
  282. package/registry/examples/iso/still.tsx +15 -0
  283. package/registry/examples/loader/endings.d.ts +4 -0
  284. package/registry/examples/loader/endings.js +14 -0
  285. package/registry/examples/loader/endings.tsx +11 -0
  286. package/registry/examples/loader/fetch-flow.d.ts +4 -0
  287. package/registry/examples/loader/fetch-flow.js +24 -0
  288. package/registry/examples/loader/fetch-flow.tsx +24 -0
  289. package/registry/examples/loader/minimal.d.ts +4 -0
  290. package/registry/examples/loader/minimal.js +11 -0
  291. package/registry/examples/loader/minimal.tsx +6 -0
  292. package/registry/examples/loader/overlay.d.ts +4 -0
  293. package/registry/examples/loader/overlay.js +16 -0
  294. package/registry/examples/loader/overlay.tsx +13 -0
  295. package/registry/examples/loader/region-handover.d.ts +4 -0
  296. package/registry/examples/loader/region-handover.js +42 -0
  297. package/registry/examples/loader/region-handover.tsx +29 -0
  298. package/registry/examples/loader/upload-eta.d.ts +4 -0
  299. package/registry/examples/loader/upload-eta.js +11 -0
  300. package/registry/examples/loader/upload-eta.tsx +10 -0
  301. package/registry/examples/marquee/cards.d.ts +4 -0
  302. package/registry/examples/marquee/cards.js +23 -0
  303. package/registry/examples/marquee/cards.tsx +21 -0
  304. package/registry/examples/marquee/logos.d.ts +4 -0
  305. package/registry/examples/marquee/logos.js +12 -0
  306. package/registry/examples/marquee/logos.tsx +17 -0
  307. package/registry/examples/media-capture/clip.d.ts +4 -0
  308. package/registry/examples/media-capture/clip.js +20 -0
  309. package/registry/examples/media-capture/clip.tsx +18 -0
  310. package/registry/examples/media-capture/native/photo.d.ts +4 -0
  311. package/registry/examples/media-capture/native/photo.dark.png +0 -0
  312. package/registry/examples/media-capture/native/photo.js +27 -0
  313. package/registry/examples/media-capture/native/photo.light.png +0 -0
  314. package/registry/examples/media-capture/native/photo.tsx +20 -0
  315. package/registry/examples/media-capture/profile-photo.d.ts +4 -0
  316. package/registry/examples/media-capture/profile-photo.js +24 -0
  317. package/registry/examples/media-capture/profile-photo.tsx +20 -0
  318. package/registry/examples/meter/balance.d.ts +4 -0
  319. package/registry/examples/meter/balance.js +14 -0
  320. package/registry/examples/meter/balance.tsx +15 -0
  321. package/registry/examples/meter/quota.d.ts +4 -0
  322. package/registry/examples/meter/quota.js +15 -0
  323. package/registry/examples/meter/quota.tsx +12 -0
  324. package/registry/examples/meter/split.d.ts +4 -0
  325. package/registry/examples/meter/split.js +18 -0
  326. package/registry/examples/meter/split.tsx +18 -0
  327. package/registry/examples/nav-bar/app-bar.d.ts +4 -0
  328. package/registry/examples/nav-bar/app-bar.js +22 -0
  329. package/registry/examples/nav-bar/app-bar.tsx +21 -0
  330. package/registry/examples/nav-bar/menu-open.d.ts +4 -0
  331. package/registry/examples/nav-bar/menu-open.js +26 -0
  332. package/registry/examples/nav-bar/menu-open.tsx +26 -0
  333. package/registry/examples/nav-bar/site.d.ts +4 -0
  334. package/registry/examples/nav-bar/site.js +31 -0
  335. package/registry/examples/nav-bar/site.tsx +36 -0
  336. package/registry/examples/number-ticker/gradient.d.ts +4 -0
  337. package/registry/examples/number-ticker/gradient.js +11 -0
  338. package/registry/examples/number-ticker/gradient.tsx +10 -0
  339. package/registry/examples/number-ticker/live-total.d.ts +4 -0
  340. package/registry/examples/number-ticker/live-total.js +17 -0
  341. package/registry/examples/number-ticker/live-total.tsx +21 -0
  342. package/registry/examples/number-ticker/stats.d.ts +4 -0
  343. package/registry/examples/number-ticker/stats.js +19 -0
  344. package/registry/examples/number-ticker/stats.tsx +23 -0
  345. package/registry/examples/pagination/cursor.d.ts +4 -0
  346. package/registry/examples/pagination/cursor.js +24 -0
  347. package/registry/examples/pagination/cursor.tsx +20 -0
  348. package/registry/examples/pagination/links.d.ts +4 -0
  349. package/registry/examples/pagination/links.js +11 -0
  350. package/registry/examples/pagination/links.tsx +6 -0
  351. package/registry/examples/pagination/native/cursor.d.ts +4 -0
  352. package/registry/examples/pagination/native/cursor.dark.png +0 -0
  353. package/registry/examples/pagination/native/cursor.js +19 -0
  354. package/registry/examples/pagination/native/cursor.light.png +0 -0
  355. package/registry/examples/pagination/native/cursor.tsx +13 -0
  356. package/registry/examples/pagination/native/pages.d.ts +4 -0
  357. package/registry/examples/pagination/native/pages.dark.png +0 -0
  358. package/registry/examples/pagination/native/pages.js +21 -0
  359. package/registry/examples/pagination/native/pages.light.png +0 -0
  360. package/registry/examples/pagination/native/pages.tsx +14 -0
  361. package/registry/examples/pagination/results.d.ts +4 -0
  362. package/registry/examples/pagination/results.js +23 -0
  363. package/registry/examples/pagination/results.tsx +19 -0
  364. package/registry/examples/pagination/table-footer.d.ts +4 -0
  365. package/registry/examples/pagination/table-footer.js +31 -0
  366. package/registry/examples/pagination/table-footer.tsx +27 -0
  367. package/registry/examples/progress/buffer.d.ts +4 -0
  368. package/registry/examples/progress/buffer.js +11 -0
  369. package/registry/examples/progress/buffer.tsx +10 -0
  370. package/registry/examples/progress/statuses.d.ts +4 -0
  371. package/registry/examples/progress/statuses.js +14 -0
  372. package/registry/examples/progress/statuses.tsx +11 -0
  373. package/registry/examples/progress/steps.d.ts +4 -0
  374. package/registry/examples/progress/steps.js +14 -0
  375. package/registry/examples/progress/steps.tsx +13 -0
  376. package/registry/examples/progress/upload.d.ts +4 -0
  377. package/registry/examples/progress/upload.js +11 -0
  378. package/registry/examples/progress/upload.tsx +10 -0
  379. package/registry/examples/search-field/global.d.ts +4 -0
  380. package/registry/examples/search-field/global.js +19 -0
  381. package/registry/examples/search-field/global.tsx +15 -0
  382. package/registry/examples/search-field/native/default.d.ts +4 -0
  383. package/registry/examples/search-field/native/default.dark.png +0 -0
  384. package/registry/examples/search-field/native/default.js +13 -0
  385. package/registry/examples/search-field/native/default.light.png +0 -0
  386. package/registry/examples/search-field/native/default.tsx +7 -0
  387. package/registry/examples/search-field/suggestions.d.ts +4 -0
  388. package/registry/examples/search-field/suggestions.js +30 -0
  389. package/registry/examples/search-field/suggestions.tsx +27 -0
  390. package/registry/examples/search-field/toolbar.d.ts +4 -0
  391. package/registry/examples/search-field/toolbar.js +17 -0
  392. package/registry/examples/search-field/toolbar.tsx +21 -0
  393. package/registry/examples/skeleton/paragraph.d.ts +4 -0
  394. package/registry/examples/skeleton/paragraph.js +14 -0
  395. package/registry/examples/skeleton/paragraph.tsx +12 -0
  396. package/registry/examples/skeleton/shape-of-content.d.ts +4 -0
  397. package/registry/examples/skeleton/shape-of-content.js +23 -0
  398. package/registry/examples/skeleton/shape-of-content.tsx +26 -0
  399. package/registry/examples/spinner/default.js +5 -2
  400. package/registry/examples/spinner/default.tsx +7 -1
  401. package/registry/examples/spinner/sizes.js +2 -1
  402. package/registry/examples/spinner/sizes.tsx +2 -0
  403. package/registry/examples/spinner/value.d.ts +4 -0
  404. package/registry/examples/spinner/value.js +11 -0
  405. package/registry/examples/spinner/value.tsx +12 -0
  406. package/registry/examples/stepper/checkout.d.ts +4 -0
  407. package/registry/examples/stepper/checkout.js +22 -0
  408. package/registry/examples/stepper/checkout.tsx +17 -0
  409. package/registry/examples/stepper/failed.d.ts +4 -0
  410. package/registry/examples/stepper/failed.js +26 -0
  411. package/registry/examples/stepper/failed.tsx +21 -0
  412. package/registry/examples/stepper/job.d.ts +4 -0
  413. package/registry/examples/stepper/job.js +32 -0
  414. package/registry/examples/stepper/job.tsx +28 -0
  415. package/registry/examples/tag-input/recipients.d.ts +4 -0
  416. package/registry/examples/tag-input/recipients.js +23 -0
  417. package/registry/examples/tag-input/recipients.tsx +20 -0
  418. package/registry/examples/tag-input/video-tags.d.ts +4 -0
  419. package/registry/examples/tag-input/video-tags.js +26 -0
  420. package/registry/examples/tag-input/video-tags.tsx +22 -0
  421. package/registry/examples/toast/grouped.d.ts +4 -0
  422. package/registry/examples/toast/grouped.js +21 -0
  423. package/registry/examples/toast/grouped.tsx +23 -0
  424. package/registry/examples/toast/undo.d.ts +4 -0
  425. package/registry/examples/toast/undo.js +29 -0
  426. package/registry/examples/toast/undo.tsx +30 -0
  427. package/registry/examples/toast/upload-progress.d.ts +4 -0
  428. package/registry/examples/toast/upload-progress.js +29 -0
  429. package/registry/examples/toast/upload-progress.tsx +31 -0
  430. package/registry/examples/video-player/default.d.ts +4 -0
  431. package/registry/examples/video-player/default.js +12 -0
  432. package/registry/examples/video-player/default.tsx +8 -0
  433. package/registry/examples/video-player/gated.d.ts +4 -0
  434. package/registry/examples/video-player/gated.js +26 -0
  435. package/registry/examples/video-player/gated.tsx +23 -0
  436. package/registry/examples/video-player/native/default.d.ts +4 -0
  437. package/registry/examples/video-player/native/default.dark.png +0 -0
  438. package/registry/examples/video-player/native/default.js +18 -0
  439. package/registry/examples/video-player/native/default.light.png +0 -0
  440. package/registry/examples/video-player/native/default.tsx +12 -0
  441. package/registry/examples/video-player/signed-source.d.ts +4 -0
  442. package/registry/examples/video-player/signed-source.js +16 -0
  443. package/registry/examples/video-player/signed-source.tsx +15 -0
  444. package/registry/examples/video-player/vertical.d.ts +4 -0
  445. package/registry/examples/video-player/vertical.js +12 -0
  446. package/registry/examples/video-player/vertical.tsx +12 -0
  447. package/registry/examples/video-trimmer/custom.d.ts +4 -0
  448. package/registry/examples/video-trimmer/custom.js +24 -0
  449. package/registry/examples/video-trimmer/custom.tsx +26 -0
  450. package/registry/examples/video-trimmer/filmstrip.d.ts +4 -0
  451. package/registry/examples/video-trimmer/filmstrip.js +15 -0
  452. package/registry/examples/video-trimmer/filmstrip.tsx +13 -0
  453. package/registry/examples/video-trimmer/playhead.d.ts +4 -0
  454. package/registry/examples/video-trimmer/playhead.js +15 -0
  455. package/registry/examples/video-trimmer/playhead.tsx +13 -0
  456. package/registry/field.json +1 -1
  457. package/registry/file-upload.json +471 -0
  458. package/registry/filter-bar.json +347 -0
  459. package/registry/glow-card.json +162 -0
  460. package/registry/image-cropper.json +313 -0
  461. package/registry/index.json +445 -20
  462. package/registry/iso.json +1355 -0
  463. package/registry/kbd.json +1 -1
  464. package/registry/loader.json +399 -0
  465. package/registry/marquee.json +225 -0
  466. package/registry/mascots.json +3 -3
  467. package/registry/media-capture.json +347 -0
  468. package/registry/meter.json +324 -0
  469. package/registry/nav-bar.json +480 -0
  470. package/registry/number-ticker.json +219 -0
  471. package/registry/page-header.json +1 -1
  472. package/registry/pagination.json +475 -0
  473. package/registry/popover.json +1 -1
  474. package/registry/progress.json +114 -9
  475. package/registry/radio-group.json +1 -1
  476. package/registry/search-field.json +436 -0
  477. package/registry/section-backdrop.json +1 -1
  478. package/registry/separator.json +1 -1
  479. package/registry/skeleton.json +69 -13
  480. package/registry/slider.json +1 -1
  481. package/registry/slot.json +1 -1
  482. package/registry/spinner.json +91 -27
  483. package/registry/stat.json +1 -1
  484. package/registry/stepper.json +174 -0
  485. package/registry/switch.json +1 -1
  486. package/registry/tabs.json +11 -7
  487. package/registry/tag-input.json +363 -0
  488. package/registry/theme.json +3 -3
  489. package/registry/toast.json +41 -13
  490. package/registry/tooltip.json +1 -1
  491. package/registry/video-player.json +598 -0
  492. package/registry/video-trimmer.json +385 -0
  493. package/dist/chunk-FHYZSO2S.js.map +0 -1
  494. package/dist/chunk-PESA2R4U.js +0 -51
  495. package/dist/chunk-PESA2R4U.js.map +0 -1
  496. package/registry/examples/spinner/muted.js +0 -11
  497. package/registry/examples/spinner/muted.tsx +0 -6
  498. /package/registry/examples/{spinner/muted.d.ts → audio-player/podcast.d.ts} +0 -0
@@ -0,0 +1,500 @@
1
+ {
2
+ "name": "data-table",
3
+ "type": "primitive",
4
+ "title": "Data table",
5
+ "summary": "A table of records — sort, selection, loading, empty and error states — that turns into cards on a phone and renders only the rows in view when there are thousands.",
6
+ "description": "A table of records — sort, selection, loading, empty and error states — that turns into cards on a phone and renders only the rows in view when there are thousands.",
7
+ "status": "beta",
8
+ "category": "data-display",
9
+ "platforms": "web",
10
+ "whenToUse": [
11
+ "To compare records on the same columns: jobs, videos of a studio, users of an admin, invoices.",
12
+ "When people sort, select several rows for a bulk action, or scan a status column.",
13
+ "With `virtualize` for a long list loaded at once (logs, 5,000 events) that must stay smooth."
14
+ ],
15
+ "whenNotToUse": [
16
+ {
17
+ "when": "For a few figures side by side: stats.",
18
+ "use": "stat"
19
+ },
20
+ {
21
+ "when": "For a gallery of media where the picture matters more than the columns: a grid of cards.",
22
+ "use": "card"
23
+ }
24
+ ],
25
+ "bestPractices": [
26
+ "Give every column a `header` (it labels the value in a card) and the row's name a `card: \"title\"` column.",
27
+ "Sort on the server (`manualSort` + `onSortChange`) when the table shows a page of a larger list.",
28
+ "Put a `Pagination` in `footer` and a `FilterBar` above; keep the filters and the page in the address.",
29
+ "Say what failed in `error` with a way to retry; say what to do next in `empty` (an `EmptyState` with an action).",
30
+ "Align amounts and counts to the end (`align: \"end\"`) in tabular numbers."
31
+ ],
32
+ "accessibility": {
33
+ "keyboard": [
34
+ {
35
+ "keys": "Tab",
36
+ "action": "Reaches the sort buttons of the headers, the checkboxes and the links of the cells."
37
+ },
38
+ {
39
+ "keys": "Space",
40
+ "action": "Toggles the focused checkbox, or sorts by the focused header."
41
+ }
42
+ ],
43
+ "notes": [
44
+ "A `table` with a `caption` (hidden unless `showCaption`) and `scope=\"col\"` headers; the roles are written out, so the cards keep the table's semantics.",
45
+ "The sorted column carries `aria-sort`; a sortable header is a button that says its state with `aria-pressed`.",
46
+ "While loading the table is `aria-busy` and its caption says so; an error is announced (`role=\"alert\"`).",
47
+ "Windowed rows carry `aria-rowindex` and the table `aria-rowcount`: a screen reader knows where it is in the whole list; the scrolling body is a focusable region."
48
+ ]
49
+ },
50
+ "related": [
51
+ "pagination",
52
+ "filter-bar",
53
+ "checkbox",
54
+ "empty-state",
55
+ "skeleton"
56
+ ],
57
+ "web": {
58
+ "entry": "@krizaka/ui/data-table",
59
+ "import": "import { DataTable } from \"@krizaka/ui/data-table\";",
60
+ "props": [
61
+ {
62
+ "component": "DataTable",
63
+ "description": "A table of records: sort, selection, loading / empty / error, cards on a phone, windowing for long lists.",
64
+ "props": [
65
+ {
66
+ "name": "caption",
67
+ "type": "string",
68
+ "default": null,
69
+ "description": "What the table lists (\"Jobs\", \"Your videos\"): its `<caption>`, hidden unless `showCaption`.",
70
+ "required": true
71
+ },
72
+ {
73
+ "name": "columns",
74
+ "type": "readonly DataTableColumn<T>[]",
75
+ "default": null,
76
+ "description": "The columns, in order.",
77
+ "required": true
78
+ },
79
+ {
80
+ "name": "getRowId",
81
+ "type": "(row: T) => string",
82
+ "default": null,
83
+ "description": "A stable id per row: the React key and the selection's value.",
84
+ "required": true
85
+ },
86
+ {
87
+ "name": "rows",
88
+ "type": "readonly T[]",
89
+ "default": null,
90
+ "description": "The rows, in the order the server gave (sorted here unless `manualSort`).",
91
+ "required": true
92
+ },
93
+ {
94
+ "name": "className",
95
+ "type": "string",
96
+ "default": null,
97
+ "description": "Classes merged on the root.",
98
+ "required": false
99
+ },
100
+ {
101
+ "name": "defaultSelected",
102
+ "type": "readonly string[]",
103
+ "default": "[]",
104
+ "description": "The ids selected at first, uncontrolled.",
105
+ "required": false
106
+ },
107
+ {
108
+ "name": "defaultSort",
109
+ "type": "DataTableSort",
110
+ "default": "null",
111
+ "description": "The sort at first, uncontrolled.",
112
+ "required": false
113
+ },
114
+ {
115
+ "name": "density",
116
+ "type": "\"comfortable\" | \"compact\"",
117
+ "default": null,
118
+ "description": "comfortable (default) · compact.",
119
+ "required": false
120
+ },
121
+ {
122
+ "name": "empty",
123
+ "type": "ReactNode",
124
+ "default": null,
125
+ "description": "Shown when there is no row (an `EmptyState`). Default: `labels.empty`.",
126
+ "required": false
127
+ },
128
+ {
129
+ "name": "error",
130
+ "type": "ReactNode",
131
+ "default": null,
132
+ "description": "A failure, shown in place of the rows (an `Alert`, a retry button); announced.",
133
+ "required": false
134
+ },
135
+ {
136
+ "name": "footer",
137
+ "type": "ReactNode",
138
+ "default": null,
139
+ "description": "Under the table: a `Pagination`, a total.",
140
+ "required": false
141
+ },
142
+ {
143
+ "name": "labels",
144
+ "type": "Partial<DataTableLabels>",
145
+ "default": null,
146
+ "description": "The words, passed translated; English by default.",
147
+ "required": false
148
+ },
149
+ {
150
+ "name": "layout",
151
+ "type": "\"table\" | \"auto\" | \"cards\"",
152
+ "default": "auto",
153
+ "description": "`auto` (default): a table, cards below 36rem of container width. `table` or `cards` to force one.",
154
+ "required": false
155
+ },
156
+ {
157
+ "name": "loading",
158
+ "type": "boolean",
159
+ "default": null,
160
+ "description": "Rows are loading: skeleton rows (`loadingRows` of them) and `aria-busy`.",
161
+ "required": false
162
+ },
163
+ {
164
+ "name": "loadingRows",
165
+ "type": "number",
166
+ "default": "5",
167
+ "description": "How many skeleton rows while loading. Default 5.",
168
+ "required": false
169
+ },
170
+ {
171
+ "name": "manualSort",
172
+ "type": "boolean",
173
+ "default": null,
174
+ "description": "The server sorts (`onSortChange` fetches): the rows are shown as given.",
175
+ "required": false
176
+ },
177
+ {
178
+ "name": "onSelectedChange",
179
+ "type": "((selected: string[]) => void)",
180
+ "default": null,
181
+ "description": "Called with the new selection.",
182
+ "required": false
183
+ },
184
+ {
185
+ "name": "onSortChange",
186
+ "type": "((sort: DataTableSort) => void)",
187
+ "default": null,
188
+ "description": "Called when a header asks for a new sort.",
189
+ "required": false
190
+ },
191
+ {
192
+ "name": "rowLabel",
193
+ "type": "((row: T) => string)",
194
+ "default": null,
195
+ "description": "The name of a row in its checkbox's label (default: its id).",
196
+ "required": false
197
+ },
198
+ {
199
+ "name": "selectable",
200
+ "type": "boolean",
201
+ "default": null,
202
+ "description": "Adds a checkbox per row and one in the header.",
203
+ "required": false
204
+ },
205
+ {
206
+ "name": "selected",
207
+ "type": "readonly string[]",
208
+ "default": null,
209
+ "description": "The ids selected (controlled).",
210
+ "required": false
211
+ },
212
+ {
213
+ "name": "showCaption",
214
+ "type": "boolean",
215
+ "default": null,
216
+ "description": "Shows the caption above the table.",
217
+ "required": false
218
+ },
219
+ {
220
+ "name": "sort",
221
+ "type": "DataTableSort",
222
+ "default": null,
223
+ "description": "The sorted column (controlled), `null` for none.",
224
+ "required": false
225
+ },
226
+ {
227
+ "name": "virtualize",
228
+ "type": "{ rowHeight: number; height: number; overscan?: number; }",
229
+ "default": null,
230
+ "description": "Renders only the rows in view (fixed `rowHeight`, a scrolling body of `height` px). Forces the table layout.",
231
+ "required": false
232
+ }
233
+ ]
234
+ }
235
+ ],
236
+ "examples": [
237
+ {
238
+ "name": "jobs",
239
+ "title": "Jobs",
240
+ "description": "Sortable columns, a status badge, row selection for a bulk action.",
241
+ "path": "examples/data-table/jobs.tsx",
242
+ "module": "@krizaka/ui/registry/examples/data-table/jobs",
243
+ "code": "\"use client\";\n\nimport { Badge } from \"@krizaka/ui/badge\";\nimport { DataTable, type DataTableColumn } from \"@krizaka/ui/data-table\";\n\ntype Job = { id: string; name: string; model: string; status: \"done\" | \"running\" | \"failed\"; seconds: number };\nconst jobs: Job[] = [\n { id: \"job-4821\", name: \"Product shots, autumn\", model: \"Flux 1.1\", status: \"done\", seconds: 42 },\n { id: \"job-4822\", name: \"Podcast transcript\", model: \"Whisper large\", status: \"running\", seconds: 318 },\n { id: \"job-4823\", name: \"Trailer voice-over\", model: \"XTTS v2\", status: \"failed\", seconds: 7 },\n { id: \"job-4824\", name: \"Catalogue summaries\", model: \"Llama 3.3\", status: \"done\", seconds: 96 },\n];\nconst tone = { done: \"success\", running: \"accent\", failed: \"danger\" } as const;\n\nconst columns: DataTableColumn<Job>[] = [\n { id: \"name\", header: \"Job\", sortable: true, card: \"title\" },\n { id: \"model\", header: \"Model\", sortable: true },\n { id: \"status\", header: \"Status\", cell: (job) => <Badge tone={tone[job.status]}>{job.status}</Badge> },\n { id: \"seconds\", header: \"Duration\", sortable: true, align: \"end\", cell: (job) => `${job.seconds} s` },\n];\n\n// Sort by a header, select rows for a bulk action; cards below 36rem of width.\nexport default function DataTableJobs() {\n return (\n <DataTable\n caption=\"Jobs\"\n rows={jobs}\n columns={columns}\n getRowId={(job) => job.id}\n rowLabel={(job) => job.name}\n defaultSort={{ column: \"seconds\", direction: \"descending\" }}\n selectable\n defaultSelected={[\"job-4823\"]}\n />\n );\n}\n"
244
+ },
245
+ {
246
+ "name": "cards",
247
+ "title": "Cards on a phone",
248
+ "description": "`layout=\"cards\"`: each row a card, the title on top — what `auto` does in a narrow container.",
249
+ "path": "examples/data-table/cards.tsx",
250
+ "module": "@krizaka/ui/registry/examples/data-table/cards",
251
+ "code": "\"use client\";\n\nimport { DataTable, type DataTableColumn } from \"@krizaka/ui/data-table\";\n\ntype Video = { id: string; title: string; views: number; tips: string; visibility: string };\nconst videos: Video[] = [\n { id: \"v1\", title: \"Sunset over Montréal\", views: 12840, tips: \"$184.00\", visibility: \"Public\" },\n { id: \"v2\", title: \"Behind the scenes, part 2\", views: 3120, tips: \"$62.50\", visibility: \"Followers\" },\n];\nconst columns: DataTableColumn<Video>[] = [\n { id: \"title\", header: \"Title\", card: \"title\" },\n { id: \"visibility\", header: \"Visibility\" },\n { id: \"views\", header: \"Views\", align: \"end\", cell: (v) => v.views.toLocaleString(\"en\") },\n { id: \"tips\", header: \"Tips\", align: \"end\" },\n];\n\n// A studio's videos on a phone: one card per row, the title on top, every value labelled.\nexport default function DataTableCards() {\n return <DataTable caption=\"Your videos\" rows={videos} columns={columns} getRowId={(v) => v.id} layout=\"cards\" />;\n}\n"
252
+ },
253
+ {
254
+ "name": "loading",
255
+ "title": "Loading",
256
+ "description": "Skeleton rows while the first page loads, the table busy.",
257
+ "path": "examples/data-table/loading.tsx",
258
+ "module": "@krizaka/ui/registry/examples/data-table/loading",
259
+ "code": "import { DataTable } from \"@krizaka/ui/data-table\";\n\nconst columns = [\n { id: \"user\", header: \"User\", card: \"title\" as const },\n { id: \"role\", header: \"Role\" },\n { id: \"joined\", header: \"Joined\", align: \"end\" as const },\n];\n\n// The first page is on its way: skeleton rows, the table busy for screen readers.\nexport default function DataTableLoading() {\n return <DataTable caption=\"Users\" rows={[]} columns={columns} getRowId={() => \"\"} loading loadingRows={4} />;\n}\n"
260
+ },
261
+ {
262
+ "name": "empty",
263
+ "title": "Empty",
264
+ "description": "No record yet: an empty state with the next step.",
265
+ "path": "examples/data-table/empty.tsx",
266
+ "module": "@krizaka/ui/registry/examples/data-table/empty",
267
+ "code": "import { Button } from \"@krizaka/ui/button\";\nimport { DataTable } from \"@krizaka/ui/data-table\";\nimport { EmptyState } from \"@krizaka/ui/empty-state\";\n\nconst columns = [\n { id: \"invoice\", header: \"Invoice\", card: \"title\" as const },\n { id: \"amount\", header: \"Amount\", align: \"end\" as const },\n];\n\n// No record yet: say what to do next.\nexport default function DataTableEmpty() {\n return (\n <DataTable\n caption=\"Invoices\"\n rows={[]}\n columns={columns}\n getRowId={() => \"\"}\n empty={<EmptyState title=\"No invoice yet\" description=\"Your first invoice appears after your first payout.\" action={<Button size=\"sm\">Set up payouts</Button>} />}\n />\n );\n}\n"
268
+ },
269
+ {
270
+ "name": "error",
271
+ "title": "Error",
272
+ "description": "The request failed: an alert with a retry, in place of the rows.",
273
+ "path": "examples/data-table/error.tsx",
274
+ "module": "@krizaka/ui/registry/examples/data-table/error",
275
+ "code": "\"use client\";\n\nimport { Button } from \"@krizaka/ui/button\";\nimport { DataTable } from \"@krizaka/ui/data-table\";\n\nconst columns = [\n { id: \"event\", header: \"Event\", card: \"title\" as const },\n { id: \"at\", header: \"At\", align: \"end\" as const },\n];\n\n// The request failed: what happened, and a way to try again — announced, in place of the rows.\nexport default function DataTableError() {\n return (\n <DataTable\n caption=\"Audit log\"\n rows={[]}\n columns={columns}\n getRowId={() => \"\"}\n error={\n <div className=\"flex flex-col items-center gap-3\">\n <p className=\"text-sm text-fg\">The audit log could not be loaded.</p>\n <Button size=\"sm\" variant=\"outline\" onClick={() => window.location.reload()}>\n Try again\n </Button>\n </div>\n }\n />\n );\n}\n"
276
+ },
277
+ {
278
+ "name": "virtualized",
279
+ "title": "5,000 rows",
280
+ "description": "`virtualize`: only the rows in view are rendered, the header stays.",
281
+ "path": "examples/data-table/virtualized.tsx",
282
+ "module": "@krizaka/ui/registry/examples/data-table/virtualized",
283
+ "code": "\"use client\";\n\nimport { DataTable, type DataTableColumn } from \"@krizaka/ui/data-table\";\n\ntype Event = { id: string; kind: string; at: string };\nconst events: Event[] = Array.from({ length: 5000 }, (_, i) => ({\n id: `evt-${i + 1}`,\n kind: [\"view\", \"tip\", \"follow\", \"comment\"][i % 4]!,\n at: `2026-10-${String(1 + (i % 11)).padStart(2, \"0\")} ${String(i % 24).padStart(2, \"0\")}:00`,\n}));\nconst columns: DataTableColumn<Event>[] = [\n { id: \"id\", header: \"Event\", sortable: true },\n { id: \"kind\", header: \"Kind\", sortable: true },\n { id: \"at\", header: \"At\", align: \"end\" },\n];\n\n// 5,000 rows, ~20 rendered: the body scrolls, the header stays.\nexport default function DataTableVirtualized() {\n return <DataTable caption=\"Events\" rows={events} columns={columns} getRowId={(e) => e.id} virtualize={{ rowHeight: 44, height: 320 }} density=\"compact\" />;\n}\n"
284
+ }
285
+ ]
286
+ },
287
+ "native": null,
288
+ "files": [
289
+ {
290
+ "path": "data-table/data-table.tsx",
291
+ "content": "// Client (sort and selection state, the windowing of long lists). A real `<table>` with a caption and column headers\n// (`scope=\"col\"`, `aria-sort` on the sorted one) that turns into a stack of cards in a narrow container — one DOM, the\n// roles written out so that the change of `display` never loses the table's semantics. Selection through\n// `@krizaka/ui/checkbox`; loading, empty and error states in the body; optional windowing for thousands of rows.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { Checkbox } from \"../checkbox\";\nimport { SortGlyph } from \"../glyphs\";\nimport { useControllable } from \"../media\";\n\nexport const dataTable = tv({\n slots: {\n root: \"@container w-full overflow-hidden rounded-xl border border-border-default bg-surface-1\",\n scroller: \"w-full overflow-x-auto\",\n table: \"w-full border-collapse text-left text-sm text-fg\",\n caption: \"px-4 pt-4 pb-2 text-left text-sm font-semibold text-fg\",\n thead: \"bg-surface-2/60\",\n headRow: \"border-b border-border-default\",\n th: \"whitespace-nowrap px-4 py-3 text-xs font-semibold text-fg-secondary\",\n sortButton:\n \"-mx-1.5 inline-flex items-center gap-1 rounded-md px-1.5 py-1 hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[pressed=true]:text-fg\",\n tbody: \"\",\n tr: \"border-b border-border-subtle transition-colors last:border-b-0 hover:bg-surface-2/50 data-[state=selected]:bg-accent-soft\",\n td: \"px-4 py-3 align-middle\",\n select: \"w-10\",\n message: \"px-4 py-10 text-center text-sm text-fg-secondary\",\n skeleton: \"h-4 w-full max-w-40 animate-pulse rounded bg-surface-3 motion-reduce:animate-none\",\n footer: \"border-t border-border-default px-4 py-3\",\n },\n variants: {\n layout: {\n table: {},\n // Below 36rem of container: cards. The sortable headers stay, as a row of sort buttons above the cards.\n auto: {\n table: \"@max-xl:block\",\n caption: \"@max-xl:block\",\n thead: \"@max-xl:sr-only @max-xl:bg-transparent @max-xl:has-[[data-keep]]:not-sr-only @max-xl:has-[[data-keep]]:block\",\n headRow: \"@max-xl:flex @max-xl:flex-wrap @max-xl:items-center @max-xl:gap-2 @max-xl:px-3 @max-xl:py-2\",\n th: \"@max-xl:sr-only @max-xl:data-[keep]:not-sr-only @max-xl:data-[keep]:block @max-xl:p-0\",\n tbody: \"@max-xl:grid @max-xl:gap-3 @max-xl:p-3\",\n tr: \"@max-xl:relative @max-xl:grid @max-xl:gap-1.5 @max-xl:rounded-xl @max-xl:border @max-xl:border-border-default @max-xl:bg-surface-0 @max-xl:p-4 @max-xl:last:border-b\",\n td:\n \"@max-xl:flex @max-xl:items-baseline @max-xl:justify-between @max-xl:gap-4 @max-xl:p-0 @max-xl:text-right \" +\n \"@max-xl:before:shrink-0 @max-xl:before:text-xs @max-xl:before:font-semibold @max-xl:before:text-fg-secondary @max-xl:before:content-[attr(data-label)] \" +\n \"@max-xl:data-[card=title]:order-first @max-xl:data-[card=title]:pr-8 @max-xl:data-[card=title]:text-left @max-xl:data-[card=title]:text-base @max-xl:data-[card=title]:font-semibold @max-xl:data-[card=title]:before:content-none \" +\n \"@max-xl:data-[card=hidden]:hidden @max-xl:data-[card=message]:block @max-xl:data-[card=message]:text-center @max-xl:data-[card=message]:before:content-none\",\n select: \"@max-xl:absolute @max-xl:right-3 @max-xl:top-3 @max-xl:w-auto @max-xl:before:content-none\",\n },\n cards: {\n table: \"block\",\n caption: \"block\",\n thead: \"sr-only bg-transparent has-[[data-keep]]:not-sr-only has-[[data-keep]]:block\",\n headRow: \"flex flex-wrap items-center gap-2 px-3 py-2\",\n th: \"sr-only p-0 data-[keep]:not-sr-only data-[keep]:block\",\n tbody: \"grid gap-3 p-3\",\n tr: \"relative grid gap-1.5 rounded-xl border border-border-default bg-surface-0 p-4 last:border-b\",\n td:\n \"flex items-baseline justify-between gap-4 p-0 text-right before:shrink-0 before:text-xs before:font-semibold before:text-fg-secondary before:content-[attr(data-label)] \" +\n \"data-[card=title]:order-first data-[card=title]:pr-8 data-[card=title]:text-left data-[card=title]:text-base data-[card=title]:font-semibold data-[card=title]:before:content-none \" +\n \"data-[card=hidden]:hidden data-[card=message]:block data-[card=message]:text-center data-[card=message]:before:content-none\",\n select: \"absolute right-3 top-3 w-auto before:content-none\",\n },\n },\n density: {\n comfortable: {},\n compact: { th: \"py-2\", td: \"py-1.5\" },\n },\n },\n defaultVariants: { layout: \"auto\", density: \"comfortable\" },\n});\n\nexport type SortDirection = \"ascending\" | \"descending\";\nexport type DataTableSort = { column: string; direction: SortDirection } | null;\ntype Sortable = string | number | boolean | Date | null | undefined;\n\nexport type DataTableColumn<T> = {\n /** A key of the column (and of the row, for the default cell and sort). */\n id: string;\n /** The column's name, passed translated: the header, and the label of the value in a card. */\n header: string;\n /** What a cell shows. Default: `value(row)`, or `row[id]`. */\n cell?: (row: T) => React.ReactNode;\n /** The value sorted on (and shown by default): a string, a number, a date. Default `row[id]`. */\n value?: (row: T) => Sortable;\n /** The header becomes a sort button (ascending → descending → none). */\n sortable?: boolean;\n /** Text alignment: `end` for amounts and counts. */\n align?: \"start\" | \"center\" | \"end\";\n /** A CSS width (`\"8rem\"`, `\"30%\"`). */\n width?: string;\n /** In a card: `title` (the heading of the card), `label` (default: \"Header value\"), `hidden`. */\n card?: \"title\" | \"label\" | \"hidden\";\n /** Hides the header visually (an actions column), keeping it for screen readers. */\n headerHidden?: boolean;\n};\n\nexport type DataTableLabels = {\n /** The checkbox of the header (\"Select all rows\"). */\n selectAll: string;\n /** The checkbox of a row: `(name) => \"Select \" + name`, the name from `rowLabel`. */\n selectRow: (name: string) => string;\n /** Read while the rows load (\"Loading\"). */\n loading: string;\n /** The default empty state (\"Nothing to show\"). */\n empty: string;\n};\n\nexport const dataTableLabels: DataTableLabels = {\n selectAll: \"Select all rows\",\n selectRow: (name) => `Select ${name}`,\n loading: \"Loading\",\n empty: \"Nothing to show\",\n};\n\nexport type DataTableProps<T> = {\n /** The rows, in the order the server gave (sorted here unless `manualSort`). */\n rows: readonly T[];\n /** The columns, in order. */\n columns: readonly DataTableColumn<T>[];\n /** A stable id per row: the React key and the selection's value. */\n getRowId: (row: T) => string;\n /** What the table lists (\"Jobs\", \"Your videos\"): its `<caption>`, hidden unless `showCaption`. */\n caption: string;\n /** Shows the caption above the table. */\n showCaption?: boolean;\n /** The sorted column (controlled), `null` for none. */\n sort?: DataTableSort;\n /** The sort at first, uncontrolled. */\n defaultSort?: DataTableSort;\n /** Called when a header asks for a new sort. */\n onSortChange?: (sort: DataTableSort) => void;\n /** The server sorts (`onSortChange` fetches): the rows are shown as given. */\n manualSort?: boolean;\n /** Adds a checkbox per row and one in the header. */\n selectable?: boolean;\n /** The ids selected (controlled). */\n selected?: readonly string[];\n /** The ids selected at first, uncontrolled. */\n defaultSelected?: readonly string[];\n /** Called with the new selection. */\n onSelectedChange?: (selected: string[]) => void;\n /** The name of a row in its checkbox's label (default: its id). */\n rowLabel?: (row: T) => string;\n /** Rows are loading: skeleton rows (`loadingRows` of them) and `aria-busy`. */\n loading?: boolean;\n /** How many skeleton rows while loading. Default 5. */\n loadingRows?: number;\n /** A failure, shown in place of the rows (an `Alert`, a retry button); announced. */\n error?: React.ReactNode;\n /** Shown when there is no row (an `EmptyState`). Default: `labels.empty`. */\n empty?: React.ReactNode;\n /** `auto` (default): a table, cards below 36rem of container width. `table` or `cards` to force one. */\n layout?: \"auto\" | \"table\" | \"cards\";\n /** comfortable (default) · compact. */\n density?: \"comfortable\" | \"compact\";\n /** Renders only the rows in view (fixed `rowHeight`, a scrolling body of `height` px). Forces the table layout. */\n virtualize?: { rowHeight: number; height: number; overscan?: number };\n /** Under the table: a `Pagination`, a total. */\n footer?: React.ReactNode;\n /** The words, passed translated; English by default. */\n labels?: Partial<DataTableLabels>;\n /** Classes merged on the root. */\n className?: string;\n};\n\nfunction compare(a: Sortable, b: Sortable): number {\n if (a == null || a === \"\") return b == null || b === \"\" ? 0 : 1;\n if (b == null || b === \"\") return -1;\n if (a instanceof Date || b instanceof Date) return Number(a) - Number(b);\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: \"base\" });\n}\n\nconst nextSort = (sort: DataTableSort, column: string): DataTableSort =>\n sort?.column !== column ? { column, direction: \"ascending\" } : sort.direction === \"ascending\" ? { column, direction: \"descending\" } : null;\n\nconst ALIGN = { start: \"text-left\", center: \"text-center\", end: \"text-right\" } as const;\n\n/** A table of records: sort, selection, loading / empty / error, cards on a phone, windowing for long lists. */\nexport function DataTable<T>({\n rows,\n columns,\n getRowId,\n caption,\n showCaption,\n sort: sortProp,\n defaultSort = null,\n onSortChange,\n manualSort,\n selectable,\n selected: selectedProp,\n defaultSelected = [],\n onSelectedChange,\n rowLabel,\n loading,\n loadingRows = 5,\n error,\n empty,\n layout = \"auto\",\n density,\n virtualize,\n footer,\n labels: given,\n className,\n}: DataTableProps<T>) {\n const labels = { ...dataTableLabels, ...given };\n const s = dataTable({ layout: virtualize ? \"table\" : layout, density });\n const [sort, setSort] = useControllable<DataTableSort>(sortProp, defaultSort, onSortChange);\n const [selected, setSelected] = useControllable<readonly string[]>(selectedProp, defaultSelected, onSelectedChange as (next: readonly string[]) => void);\n const [scrollTop, setScrollTop] = React.useState(0);\n\n const valueOf = (column: DataTableColumn<T>, row: T): Sortable =>\n column.value ? column.value(row) : ((row as Record<string, unknown>)[column.id] as Sortable);\n\n const sorted = React.useMemo(() => {\n const column = sort && columns.find((c) => c.id === sort.column);\n if (!column || manualSort) return rows;\n const factor = sort.direction === \"ascending\" ? 1 : -1;\n return [...rows].sort((a, b) => factor * compare(valueOf(column, a), valueOf(column, b)));\n // eslint-disable-next-line react-hooks/exhaustive-deps -- valueOf only reads `column`\n }, [rows, columns, sort, manualSort]);\n\n const ids = sorted.map(getRowId);\n const chosen = new Set(selected);\n const allChosen = ids.length > 0 && ids.every((id) => chosen.has(id));\n const someChosen = ids.some((id) => chosen.has(id));\n const toggleAll = () => setSelected(allChosen ? selected.filter((id) => !ids.includes(id)) : [...new Set([...selected, ...ids])]);\n const toggle = (id: string) => setSelected(chosen.has(id) ? selected.filter((x) => x !== id) : [...selected, id]);\n\n const span = columns.length + (selectable ? 1 : 0);\n let windowed = sorted;\n let before = 0;\n let after = 0;\n let offset = 0;\n if (virtualize && !loading) {\n const overscan = virtualize.overscan ?? 6;\n const start = Math.max(0, Math.floor(scrollTop / virtualize.rowHeight) - overscan);\n const end = Math.min(sorted.length, Math.ceil((scrollTop + virtualize.height) / virtualize.rowHeight) + overscan);\n windowed = sorted.slice(start, end);\n before = start * virtualize.rowHeight;\n after = (sorted.length - end) * virtualize.rowHeight;\n offset = start;\n }\n\n const message = (content: React.ReactNode, alert?: boolean) => (\n <tr role=\"row\" className={s.tr({ className: \"hover:bg-transparent\" })}>\n <td role=\"cell\" colSpan={span} data-card=\"message\" className={s.td({ className: s.message() })}>\n {alert ? <div role=\"alert\">{content}</div> : content}\n </td>\n </tr>\n );\n\n let body: React.ReactNode;\n if (loading) {\n body = Array.from({ length: loadingRows }, (_, i) => (\n <tr role=\"row\" key={`loading-${i}`} aria-hidden className={s.tr()}>\n {selectable && <td role=\"cell\" className={s.td({ className: s.select() })} />}\n {columns.map((column) => (\n <td role=\"cell\" key={column.id} data-card={column.card === \"title\" ? \"title\" : column.card === \"hidden\" ? \"hidden\" : undefined} className={s.td()}>\n <div className={s.skeleton()} />\n </td>\n ))}\n </tr>\n ));\n } else if (error) body = message(error, true);\n else if (sorted.length === 0) body = message(empty ?? labels.empty);\n else {\n body = windowed.map((row, i) => {\n const id = getRowId(row);\n const isChosen = chosen.has(id);\n return (\n <tr\n role=\"row\"\n key={id}\n aria-rowindex={virtualize ? offset + i + 2 : undefined}\n data-state={isChosen ? \"selected\" : undefined}\n className={s.tr()}\n style={virtualize ? { height: virtualize.rowHeight } : undefined}\n >\n {selectable && (\n <td role=\"cell\" className={s.td({ className: s.select() })}>\n <Checkbox checked={isChosen} onCheckedChange={() => toggle(id)} aria-label={labels.selectRow(rowLabel ? rowLabel(row) : id)} />\n </td>\n )}\n {columns.map((column) => (\n <td\n role=\"cell\"\n key={column.id}\n data-label={column.header}\n data-card={column.card === \"title\" ? \"title\" : column.card === \"hidden\" ? \"hidden\" : undefined}\n className={s.td({ className: ALIGN[column.align ?? \"start\"] })}\n >\n {column.cell ? column.cell(row) : String(valueOf(column, row) ?? \"\")}\n </td>\n ))}\n </tr>\n );\n });\n }\n\n const table = (\n <table\n role=\"table\"\n aria-busy={loading || undefined}\n aria-rowcount={virtualize ? sorted.length + 1 : undefined}\n className={s.table()}\n >\n <caption className={showCaption ? s.caption() : \"sr-only\"}>\n {caption}\n {loading ? <span className=\"sr-only\">{` — ${labels.loading}`}</span> : null}\n </caption>\n <thead role=\"rowgroup\" className={s.thead({ className: virtualize ? \"sticky top-0 z-10 bg-surface-2\" : undefined })}>\n <tr role=\"row\" aria-rowindex={virtualize ? 1 : undefined} className={s.headRow()}>\n {selectable && (\n <th role=\"columnheader\" scope=\"col\" data-keep=\"\" className={s.th({ className: s.select() })}>\n <Checkbox\n checked={allChosen ? true : someChosen ? \"indeterminate\" : false}\n onCheckedChange={toggleAll}\n disabled={loading || ids.length === 0}\n aria-label={labels.selectAll}\n />\n </th>\n )}\n {columns.map((column) => {\n const direction = sort?.column === column.id ? sort.direction : undefined;\n return (\n <th\n role=\"columnheader\"\n scope=\"col\"\n key={column.id}\n aria-sort={column.sortable ? (direction ?? \"none\") : undefined}\n data-keep={column.sortable ? \"\" : undefined}\n className={s.th({ className: ALIGN[column.align ?? \"start\"] })}\n style={column.width ? { width: column.width } : undefined}\n >\n {column.sortable ? (\n <button type=\"button\" aria-pressed={Boolean(direction)} className={s.sortButton()} onClick={() => setSort(nextSort(sort, column.id))}>\n {column.header}\n <SortGlyph direction={direction} className=\"h-3.5 w-3.5\" />\n </button>\n ) : column.headerHidden ? (\n <span className=\"sr-only\">{column.header}</span>\n ) : (\n column.header\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody role=\"rowgroup\" className={s.tbody()}>\n {before > 0 && (\n <tr aria-hidden style={{ height: before }}>\n <td colSpan={span} />\n </tr>\n )}\n {body}\n {after > 0 && (\n <tr aria-hidden style={{ height: after }}>\n <td colSpan={span} />\n </tr>\n )}\n </tbody>\n </table>\n );\n\n return (\n <div className={s.root({ className })}>\n {virtualize ? (\n // A scrolling region the keyboard can reach: Tab lands on it, the arrows scroll it.\n <div\n tabIndex={0}\n role=\"region\"\n aria-label={caption}\n className={s.scroller({ className: \"overflow-y-auto focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\" })}\n style={{ height: virtualize.height }}\n onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}\n >\n {table}\n </div>\n ) : (\n <div className={s.scroller()}>{table}</div>\n )}\n {footer ? <div className={s.footer()}>{footer}</div> : null}\n </div>\n );\n}\n"
292
+ },
293
+ {
294
+ "path": "data-table/index.ts",
295
+ "content": "/** @krizaka/ui/data-table — `DataTable` (sort, selection, loading / empty / error, cards on a phone, windowing), `dataTable` (the slots). Client (\"use client\"). */\nexport {\n DataTable,\n dataTable,\n type DataTableColumn,\n type DataTableLabels,\n dataTableLabels,\n type DataTableProps,\n type DataTableSort,\n type SortDirection,\n} from \"./data-table\";\n"
296
+ },
297
+ {
298
+ "path": "data-table/meta.ts",
299
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Data table\",\n summary: \"A table of records — sort, selection, loading, empty and error states — that turns into cards on a phone and renders only the rows in view when there are thousands.\",\n status: \"beta\",\n category: \"data-display\",\n platforms: \"web\",\n whenToUse: [\n \"To compare records on the same columns: jobs, videos of a studio, users of an admin, invoices.\",\n \"When people sort, select several rows for a bulk action, or scan a status column.\",\n \"With `virtualize` for a long list loaded at once (logs, 5,000 events) that must stay smooth.\",\n ],\n whenNotToUse: [\n { when: \"For a few figures side by side: stats.\", use: \"stat\" },\n { when: \"For a gallery of media where the picture matters more than the columns: a grid of cards.\", use: \"card\" },\n ],\n bestPractices: [\n \"Give every column a `header` (it labels the value in a card) and the row's name a `card: \\\"title\\\"` column.\",\n \"Sort on the server (`manualSort` + `onSortChange`) when the table shows a page of a larger list.\",\n \"Put a `Pagination` in `footer` and a `FilterBar` above; keep the filters and the page in the address.\",\n \"Say what failed in `error` with a way to retry; say what to do next in `empty` (an `EmptyState` with an action).\",\n \"Align amounts and counts to the end (`align: \\\"end\\\"`) in tabular numbers.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Tab\", action: \"Reaches the sort buttons of the headers, the checkboxes and the links of the cells.\" },\n { keys: \"Space\", action: \"Toggles the focused checkbox, or sorts by the focused header.\" },\n ],\n notes: [\n \"A `table` with a `caption` (hidden unless `showCaption`) and `scope=\\\"col\\\"` headers; the roles are written out, so the cards keep the table's semantics.\",\n \"The sorted column carries `aria-sort`; a sortable header is a button that says its state with `aria-pressed`.\",\n \"While loading the table is `aria-busy` and its caption says so; an error is announced (`role=\\\"alert\\\"`).\",\n \"Windowed rows carry `aria-rowindex` and the table `aria-rowcount`: a screen reader knows where it is in the whole list; the scrolling body is a focusable region.\",\n ],\n },\n related: [\"pagination\", \"filter-bar\", \"checkbox\", \"empty-state\", \"skeleton\"],\n web: {\n imports: [\"DataTable\"],\n examples: [\n { name: \"jobs\", title: \"Jobs\", description: \"Sortable columns, a status badge, row selection for a bulk action.\" },\n { name: \"cards\", title: \"Cards on a phone\", description: \"`layout=\\\"cards\\\"`: each row a card, the title on top — what `auto` does in a narrow container.\" },\n { name: \"loading\", title: \"Loading\", description: \"Skeleton rows while the first page loads, the table busy.\" },\n { name: \"empty\", title: \"Empty\", description: \"No record yet: an empty state with the next step.\" },\n { name: \"error\", title: \"Error\", description: \"The request failed: an alert with a retry, in place of the rows.\" },\n { name: \"virtualized\", title: \"5,000 rows\", description: \"`virtualize`: only the rows in view are rendered, the header stays.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
300
+ },
301
+ {
302
+ "path": "glyphs.tsx",
303
+ "content": "// The small line glyphs the data and media primitives draw (play, volume, search, chevrons…): decorative, 24-unit\n// box, `currentColor`, sized by the caller's class. Shared by the entries that need them; never exported on its own —\n// a product draws its own icons with @krizaka/icons.\nimport type * as React from \"react\";\n\ntype GlyphProps = { className?: string };\n\nfunction Glyph({ className = \"h-4 w-4\", fill, children }: GlyphProps & { fill?: boolean; children: React.ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n className={className}\n fill={fill ? \"currentColor\" : \"none\"}\n stroke={fill ? \"none\" : \"currentColor\"}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden\n focusable=\"false\"\n >\n {children}\n </svg>\n );\n}\n\nexport const PlayGlyph = (p: GlyphProps) => (\n <Glyph {...p} fill>\n <path d=\"M8 5.5v13a1 1 0 0 0 1.5.86l10.4-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5Z\" />\n </Glyph>\n);\nexport const PauseGlyph = (p: GlyphProps) => (\n <Glyph {...p} fill>\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </Glyph>\n);\nexport const VolumeGlyph = ({ muted, ...p }: GlyphProps & { muted?: boolean }) => (\n <Glyph {...p}>\n <path d=\"M11 5 6 9H3v6h3l5 4V5Z\" />\n {muted ? <path d=\"m22 9-6 6m0-6 6 6\" /> : <path d=\"M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13\" />}\n </Glyph>\n);\nexport const FullscreenGlyph = ({ exit, ...p }: GlyphProps & { exit?: boolean }) => (\n <Glyph {...p}>\n {exit ? <path d=\"M9 4v5H4m11-5v5h5M9 20v-5H4m11 5v-5h5\" /> : <path d=\"M4 9V4h5m6 0h5v5M4 15v5h5m11-5v5h-5\" />}\n </Glyph>\n);\nexport const CaptionsGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"M10 10.5a2 2 0 1 0 0 3M17 10.5a2 2 0 1 0 0 3\" />\n </Glyph>\n);\nexport const PipGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <rect x=\"12\" y=\"12\" width=\"6\" height=\"4\" rx=\"1\" />\n </Glyph>\n);\nexport const SearchGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.5-3.5\" />\n </Glyph>\n);\nexport const CloseGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </Glyph>\n);\nexport const ChevronGlyph = ({ direction = \"down\", ...p }: GlyphProps & { direction?: \"left\" | \"right\" | \"up\" | \"down\" }) => (\n <Glyph {...p}>\n <path d={{ left: \"m15 6-6 6 6 6\", right: \"m9 6 6 6-6 6\", up: \"m6 15 6-6 6 6\", down: \"m6 9 6 6 6-6\" }[direction]} />\n </Glyph>\n);\nexport const CheckGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M20 6 9 17l-5-5\" />\n </Glyph>\n);\nexport const PlusGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M12 5v14M5 12h14\" />\n </Glyph>\n);\nexport const CalendarGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"16\" rx=\"2\" />\n <path d=\"M3 10h18M8 3v4M16 3v4\" />\n </Glyph>\n);\nexport const UploadGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M12 16V4m0 0L7 9m5-5 5 5\" />\n <path d=\"M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\" />\n </Glyph>\n);\nexport const FileGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z\" />\n <path d=\"M14 3v5h5\" />\n </Glyph>\n);\nexport const CameraGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 8h3l2-3h6l2 3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1Z\" />\n <circle cx=\"12\" cy=\"13.5\" r=\"3.5\" />\n </Glyph>\n);\nexport const FlipGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 12a8 8 0 0 1 14-5.3M20 12a8 8 0 0 1-14 5.3\" />\n <path d=\"M18 3v4h-4M6 21v-4h4\" />\n </Glyph>\n);\nexport const RetryGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 12a8 8 0 1 0 2.3-5.7L4 8.5\" />\n <path d=\"M4 4v4.5h4.5\" />\n </Glyph>\n);\nexport const SortGlyph = ({ direction, ...p }: GlyphProps & { direction?: \"ascending\" | \"descending\" }) => (\n <Glyph {...p}>\n {direction === \"ascending\" ? <path d=\"m7 14 5-5 5 5\" /> : direction === \"descending\" ? <path d=\"m7 10 5 5 5-5\" /> : <path d=\"m8 9 4-4 4 4M8 15l4 4 4-4\" />}\n </Glyph>\n);\n"
304
+ },
305
+ {
306
+ "path": "media.ts",
307
+ "content": "// Shared by the data and media primitives (client): the clock format, the reduced-motion preference, the\n// in-view observer, a debounced callback. Never an entry of its own.\nimport * as React from \"react\";\n\n/** Seconds as a clock: `75` → `1:15`, `3725` → `1:02:05`. Negative or unknown values read `0:00`. */\nexport function formatClock(seconds: number): string {\n const s = Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0;\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const rest = String(s % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${rest}` : `${m}:${rest}`;\n}\n\n/** Clamps `value` between `min` and `max`. */\nexport const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst REDUCE = \"(prefers-reduced-motion: reduce)\";\nconst subscribeReduce = (notify: () => void) => {\n if (typeof window === \"undefined\" || !window.matchMedia) return () => undefined;\n const query = window.matchMedia(REDUCE);\n query.addEventListener?.(\"change\", notify);\n return () => query.removeEventListener?.(\"change\", notify);\n};\n\n/** `true` when the person asked for less motion (`prefers-reduced-motion: reduce`); follows a change of the setting. */\nexport function usePrefersReducedMotion(): boolean {\n return React.useSyncExternalStore(\n subscribeReduce,\n () => (typeof window !== \"undefined\" && window.matchMedia ? window.matchMedia(REDUCE).matches : false),\n () => false,\n );\n}\n\n/** The latest value of a callback, for an effect or an event that must not re-subscribe when it changes. */\nexport function useLatest<T>(value: T) {\n const ref = React.useRef(value);\n React.useLayoutEffect(() => {\n ref.current = value;\n });\n return ref;\n}\n\n/** Calls `onChange(visible)` when at least `threshold` of the element enters or leaves the viewport. */\nexport function useInView(ref: React.RefObject<Element | null>, onChange: (visible: boolean) => void, enabled = true, threshold = 0.25) {\n const latest = useLatest(onChange);\n React.useEffect(() => {\n const element = ref.current;\n if (!enabled || !element || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver(([entry]) => entry && latest.current(entry.isIntersecting), { threshold });\n observer.observe(element);\n return () => observer.disconnect();\n }, [ref, enabled, threshold, latest]);\n}\n\n/** A stable function that runs `callback` once `delay` ms have passed without a new call; `cancel()` drops the pending one. */\nexport function useDebouncedCallback<A extends unknown[]>(callback: (...args: A) => void, delay: number) {\n const latest = useLatest(callback);\n const [debounced] = React.useState(() => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let wait = delay;\n const run = (...args: A) => {\n clearTimeout(timer);\n if (wait <= 0) latest.current(...args);\n else timer = setTimeout(() => latest.current(...args), wait);\n };\n return Object.assign(run, {\n cancel: () => clearTimeout(timer),\n setDelay: (next: number) => {\n wait = next;\n },\n });\n });\n React.useEffect(() => debounced.setDelay(delay), [debounced, delay]);\n React.useEffect(() => () => debounced.cancel(), [debounced]);\n return debounced;\n}\n\n/** A value controlled by a prop when it is given, held inside otherwise; `set` calls `onChange` either way. */\nexport function useControllable<T>(value: T | undefined, defaultValue: T, onChange?: (next: T) => void) {\n const [inner, setInner] = React.useState(defaultValue);\n const controlled = value !== undefined;\n const current = controlled ? value : inner;\n const set = React.useCallback(\n (next: T) => {\n if (!controlled) setInner(next);\n onChange?.(next);\n },\n [controlled, onChange],\n );\n return [current, set] as const;\n}\n\n/** Starts a media element without an unhandled rejection (autoplay refused, the source replaced meanwhile). */\nexport function playSafely(media: HTMLMediaElement | null | undefined) {\n try {\n const started = media?.play() as Promise<void> | undefined;\n started?.catch?.(() => undefined);\n } catch {\n // Not playable here (no media support): the controls stay as they are.\n }\n}\n"
308
+ },
309
+ {
310
+ "path": "meta.ts",
311
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which krizaka.com renders as\n// /docs/ui/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"alert\"\n | \"audio-player\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"cn\"\n | \"combobox\"\n | \"command\"\n | \"confirm-button\"\n | \"countdown\"\n | \"data-table\"\n | \"date-picker\"\n | \"dialog\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"glow-card\"\n | \"image-cropper\"\n | \"iso\"\n | \"kbd\"\n | \"loader\"\n | \"marquee\"\n | \"mascots\"\n | \"media-capture\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"page-header\"\n | \"pagination\"\n | \"popover\"\n | \"progress\"\n | \"radio-group\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"skeleton\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"stat\"\n | \"stepper\"\n | \"switch\"\n | \"tabs\"\n | \"tag-input\"\n | \"theme\"\n | \"toast\"\n | \"tooltip\"\n | \"txt\"\n | \"video-player\"\n | \"video-trimmer\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/** `stable`: its API only changes in a major. `beta`: it may still change in a minor. */\nexport type Status = \"stable\" | \"beta\";\n\n/** The group of the catalogue. `media`: video, sound, camera, files, cropping. */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"layout\" | \"foundations\";\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\nexport interface Example {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
312
+ }
313
+ ],
314
+ "dependencies": [
315
+ "tailwind-variants@^3.3.1"
316
+ ],
317
+ "registryDependencies": [
318
+ "checkbox"
319
+ ],
320
+ "demo": {
321
+ "path": "examples/data-table/jobs.tsx",
322
+ "content": "\"use client\";\n\nimport { Badge } from \"@krizaka/ui/badge\";\nimport { DataTable, type DataTableColumn } from \"@krizaka/ui/data-table\";\n\ntype Job = { id: string; name: string; model: string; status: \"done\" | \"running\" | \"failed\"; seconds: number };\nconst jobs: Job[] = [\n { id: \"job-4821\", name: \"Product shots, autumn\", model: \"Flux 1.1\", status: \"done\", seconds: 42 },\n { id: \"job-4822\", name: \"Podcast transcript\", model: \"Whisper large\", status: \"running\", seconds: 318 },\n { id: \"job-4823\", name: \"Trailer voice-over\", model: \"XTTS v2\", status: \"failed\", seconds: 7 },\n { id: \"job-4824\", name: \"Catalogue summaries\", model: \"Llama 3.3\", status: \"done\", seconds: 96 },\n];\nconst tone = { done: \"success\", running: \"accent\", failed: \"danger\" } as const;\n\nconst columns: DataTableColumn<Job>[] = [\n { id: \"name\", header: \"Job\", sortable: true, card: \"title\" },\n { id: \"model\", header: \"Model\", sortable: true },\n { id: \"status\", header: \"Status\", cell: (job) => <Badge tone={tone[job.status]}>{job.status}</Badge> },\n { id: \"seconds\", header: \"Duration\", sortable: true, align: \"end\", cell: (job) => `${job.seconds} s` },\n];\n\n// Sort by a header, select rows for a bulk action; cards below 36rem of width.\nexport default function DataTableJobs() {\n return (\n <DataTable\n caption=\"Jobs\"\n rows={jobs}\n columns={columns}\n getRowId={(job) => job.id}\n rowLabel={(job) => job.name}\n defaultSort={{ column: \"seconds\", direction: \"descending\" }}\n selectable\n defaultSelected={[\"job-4823\"]}\n />\n );\n}\n"
323
+ },
324
+ "props": [
325
+ {
326
+ "component": "DataTable",
327
+ "description": "A table of records: sort, selection, loading / empty / error, cards on a phone, windowing for long lists.",
328
+ "props": [
329
+ {
330
+ "name": "caption",
331
+ "type": "string",
332
+ "default": null,
333
+ "description": "What the table lists (\"Jobs\", \"Your videos\"): its `<caption>`, hidden unless `showCaption`.",
334
+ "required": true
335
+ },
336
+ {
337
+ "name": "columns",
338
+ "type": "readonly DataTableColumn<T>[]",
339
+ "default": null,
340
+ "description": "The columns, in order.",
341
+ "required": true
342
+ },
343
+ {
344
+ "name": "getRowId",
345
+ "type": "(row: T) => string",
346
+ "default": null,
347
+ "description": "A stable id per row: the React key and the selection's value.",
348
+ "required": true
349
+ },
350
+ {
351
+ "name": "rows",
352
+ "type": "readonly T[]",
353
+ "default": null,
354
+ "description": "The rows, in the order the server gave (sorted here unless `manualSort`).",
355
+ "required": true
356
+ },
357
+ {
358
+ "name": "className",
359
+ "type": "string",
360
+ "default": null,
361
+ "description": "Classes merged on the root.",
362
+ "required": false
363
+ },
364
+ {
365
+ "name": "defaultSelected",
366
+ "type": "readonly string[]",
367
+ "default": "[]",
368
+ "description": "The ids selected at first, uncontrolled.",
369
+ "required": false
370
+ },
371
+ {
372
+ "name": "defaultSort",
373
+ "type": "DataTableSort",
374
+ "default": "null",
375
+ "description": "The sort at first, uncontrolled.",
376
+ "required": false
377
+ },
378
+ {
379
+ "name": "density",
380
+ "type": "\"comfortable\" | \"compact\"",
381
+ "default": null,
382
+ "description": "comfortable (default) · compact.",
383
+ "required": false
384
+ },
385
+ {
386
+ "name": "empty",
387
+ "type": "ReactNode",
388
+ "default": null,
389
+ "description": "Shown when there is no row (an `EmptyState`). Default: `labels.empty`.",
390
+ "required": false
391
+ },
392
+ {
393
+ "name": "error",
394
+ "type": "ReactNode",
395
+ "default": null,
396
+ "description": "A failure, shown in place of the rows (an `Alert`, a retry button); announced.",
397
+ "required": false
398
+ },
399
+ {
400
+ "name": "footer",
401
+ "type": "ReactNode",
402
+ "default": null,
403
+ "description": "Under the table: a `Pagination`, a total.",
404
+ "required": false
405
+ },
406
+ {
407
+ "name": "labels",
408
+ "type": "Partial<DataTableLabels>",
409
+ "default": null,
410
+ "description": "The words, passed translated; English by default.",
411
+ "required": false
412
+ },
413
+ {
414
+ "name": "layout",
415
+ "type": "\"table\" | \"auto\" | \"cards\"",
416
+ "default": "auto",
417
+ "description": "`auto` (default): a table, cards below 36rem of container width. `table` or `cards` to force one.",
418
+ "required": false
419
+ },
420
+ {
421
+ "name": "loading",
422
+ "type": "boolean",
423
+ "default": null,
424
+ "description": "Rows are loading: skeleton rows (`loadingRows` of them) and `aria-busy`.",
425
+ "required": false
426
+ },
427
+ {
428
+ "name": "loadingRows",
429
+ "type": "number",
430
+ "default": "5",
431
+ "description": "How many skeleton rows while loading. Default 5.",
432
+ "required": false
433
+ },
434
+ {
435
+ "name": "manualSort",
436
+ "type": "boolean",
437
+ "default": null,
438
+ "description": "The server sorts (`onSortChange` fetches): the rows are shown as given.",
439
+ "required": false
440
+ },
441
+ {
442
+ "name": "onSelectedChange",
443
+ "type": "((selected: string[]) => void)",
444
+ "default": null,
445
+ "description": "Called with the new selection.",
446
+ "required": false
447
+ },
448
+ {
449
+ "name": "onSortChange",
450
+ "type": "((sort: DataTableSort) => void)",
451
+ "default": null,
452
+ "description": "Called when a header asks for a new sort.",
453
+ "required": false
454
+ },
455
+ {
456
+ "name": "rowLabel",
457
+ "type": "((row: T) => string)",
458
+ "default": null,
459
+ "description": "The name of a row in its checkbox's label (default: its id).",
460
+ "required": false
461
+ },
462
+ {
463
+ "name": "selectable",
464
+ "type": "boolean",
465
+ "default": null,
466
+ "description": "Adds a checkbox per row and one in the header.",
467
+ "required": false
468
+ },
469
+ {
470
+ "name": "selected",
471
+ "type": "readonly string[]",
472
+ "default": null,
473
+ "description": "The ids selected (controlled).",
474
+ "required": false
475
+ },
476
+ {
477
+ "name": "showCaption",
478
+ "type": "boolean",
479
+ "default": null,
480
+ "description": "Shows the caption above the table.",
481
+ "required": false
482
+ },
483
+ {
484
+ "name": "sort",
485
+ "type": "DataTableSort",
486
+ "default": null,
487
+ "description": "The sorted column (controlled), `null` for none.",
488
+ "required": false
489
+ },
490
+ {
491
+ "name": "virtualize",
492
+ "type": "{ rowHeight: number; height: number; overscan?: number; }",
493
+ "default": null,
494
+ "description": "Renders only the rows in view (fixed `rowHeight`, a scrolling body of `height` px). Forces the table layout.",
495
+ "required": false
496
+ }
497
+ ]
498
+ }
499
+ ]
500
+ }