@poodle64/ui 2026.8.7

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 (387) hide show
  1. package/README.md +624 -0
  2. package/dist/components/ui/alert/alert-description.svelte +23 -0
  3. package/dist/components/ui/alert/alert-description.svelte.d.ts +9 -0
  4. package/dist/components/ui/alert/alert-title.svelte +23 -0
  5. package/dist/components/ui/alert/alert-title.svelte.d.ts +9 -0
  6. package/dist/components/ui/alert/alert.svelte +46 -0
  7. package/dist/components/ui/alert/alert.svelte.d.ts +28 -0
  8. package/dist/components/ui/alert/index.d.ts +4 -0
  9. package/dist/components/ui/alert/index.js +4 -0
  10. package/dist/components/ui/alert-dialog/alert-dialog-action.svelte +27 -0
  11. package/dist/components/ui/alert-dialog/alert-dialog-action.svelte.d.ts +9 -0
  12. package/dist/components/ui/alert-dialog/alert-dialog-cancel.svelte +27 -0
  13. package/dist/components/ui/alert-dialog/alert-dialog-cancel.svelte.d.ts +9 -0
  14. package/dist/components/ui/alert-dialog/alert-dialog-content.svelte +39 -0
  15. package/dist/components/ui/alert-dialog/alert-dialog-content.svelte.d.ts +11 -0
  16. package/dist/components/ui/alert-dialog/alert-dialog-description.svelte +20 -0
  17. package/dist/components/ui/alert-dialog/alert-dialog-description.svelte.d.ts +4 -0
  18. package/dist/components/ui/alert-dialog/alert-dialog-footer.svelte +23 -0
  19. package/dist/components/ui/alert-dialog/alert-dialog-footer.svelte.d.ts +5 -0
  20. package/dist/components/ui/alert-dialog/alert-dialog-header.svelte +23 -0
  21. package/dist/components/ui/alert-dialog/alert-dialog-header.svelte.d.ts +5 -0
  22. package/dist/components/ui/alert-dialog/alert-dialog-media.svelte +23 -0
  23. package/dist/components/ui/alert-dialog/alert-dialog-media.svelte.d.ts +5 -0
  24. package/dist/components/ui/alert-dialog/alert-dialog-overlay.svelte +20 -0
  25. package/dist/components/ui/alert-dialog/alert-dialog-overlay.svelte.d.ts +4 -0
  26. package/dist/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
  27. package/dist/components/ui/alert-dialog/alert-dialog-portal.svelte.d.ts +3 -0
  28. package/dist/components/ui/alert-dialog/alert-dialog-title.svelte +20 -0
  29. package/dist/components/ui/alert-dialog/alert-dialog-title.svelte.d.ts +4 -0
  30. package/dist/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
  31. package/dist/components/ui/alert-dialog/alert-dialog-trigger.svelte.d.ts +4 -0
  32. package/dist/components/ui/alert-dialog/alert-dialog.svelte +7 -0
  33. package/dist/components/ui/alert-dialog/alert-dialog.svelte.d.ts +3 -0
  34. package/dist/components/ui/alert-dialog/index.d.ts +13 -0
  35. package/dist/components/ui/alert-dialog/index.js +15 -0
  36. package/dist/components/ui/app-dialog/app-dialog.svelte +111 -0
  37. package/dist/components/ui/app-dialog/app-dialog.svelte.d.ts +41 -0
  38. package/dist/components/ui/app-dialog/index.d.ts +2 -0
  39. package/dist/components/ui/app-dialog/index.js +2 -0
  40. package/dist/components/ui/app-shell/app-nav.svelte +299 -0
  41. package/dist/components/ui/app-shell/app-nav.svelte.d.ts +22 -0
  42. package/dist/components/ui/app-shell/app-shell.svelte +675 -0
  43. package/dist/components/ui/app-shell/app-shell.svelte.d.ts +157 -0
  44. package/dist/components/ui/app-shell/index.d.ts +6 -0
  45. package/dist/components/ui/app-shell/index.js +6 -0
  46. package/dist/components/ui/app-shell/measure.d.ts +28 -0
  47. package/dist/components/ui/app-shell/measure.js +21 -0
  48. package/dist/components/ui/app-shell/texture.d.ts +32 -0
  49. package/dist/components/ui/app-shell/texture.js +27 -0
  50. package/dist/components/ui/app-shell/types.d.ts +134 -0
  51. package/dist/components/ui/app-shell/types.js +88 -0
  52. package/dist/components/ui/arc-gauge/arc-gauge.svelte +112 -0
  53. package/dist/components/ui/arc-gauge/arc-gauge.svelte.d.ts +15 -0
  54. package/dist/components/ui/arc-gauge/index.d.ts +3 -0
  55. package/dist/components/ui/arc-gauge/index.js +2 -0
  56. package/dist/components/ui/avatar/avatar-fallback.svelte +26 -0
  57. package/dist/components/ui/avatar/avatar-fallback.svelte.d.ts +4 -0
  58. package/dist/components/ui/avatar/avatar-image.svelte +17 -0
  59. package/dist/components/ui/avatar/avatar-image.svelte.d.ts +4 -0
  60. package/dist/components/ui/avatar/avatar.svelte +31 -0
  61. package/dist/components/ui/avatar/avatar.svelte.d.ts +4 -0
  62. package/dist/components/ui/avatar/index.d.ts +4 -0
  63. package/dist/components/ui/avatar/index.js +6 -0
  64. package/dist/components/ui/badge/badge.svelte +50 -0
  65. package/dist/components/ui/badge/badge.svelte.d.ts +38 -0
  66. package/dist/components/ui/badge/index.d.ts +2 -0
  67. package/dist/components/ui/badge/index.js +2 -0
  68. package/dist/components/ui/bar-row/bar-row.svelte +42 -0
  69. package/dist/components/ui/bar-row/bar-row.svelte.d.ts +12 -0
  70. package/dist/components/ui/bar-row/index.d.ts +2 -0
  71. package/dist/components/ui/bar-row/index.js +2 -0
  72. package/dist/components/ui/button/button.svelte +89 -0
  73. package/dist/components/ui/button/button.svelte.d.ts +70 -0
  74. package/dist/components/ui/button/index.d.ts +2 -0
  75. package/dist/components/ui/button/index.js +4 -0
  76. package/dist/components/ui/card/card-action.svelte +23 -0
  77. package/dist/components/ui/card/card-action.svelte.d.ts +5 -0
  78. package/dist/components/ui/card/card-content.svelte +20 -0
  79. package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
  80. package/dist/components/ui/card/card-description.svelte +20 -0
  81. package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
  82. package/dist/components/ui/card/card-footer.svelte +23 -0
  83. package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
  84. package/dist/components/ui/card/card-header.svelte +23 -0
  85. package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
  86. package/dist/components/ui/card/card-title.svelte +52 -0
  87. package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
  88. package/dist/components/ui/card/card.svelte +25 -0
  89. package/dist/components/ui/card/card.svelte.d.ts +8 -0
  90. package/dist/components/ui/card/index.d.ts +8 -0
  91. package/dist/components/ui/card/index.js +10 -0
  92. package/dist/components/ui/checkbox/checkbox.svelte +36 -0
  93. package/dist/components/ui/checkbox/checkbox.svelte.d.ts +8 -0
  94. package/dist/components/ui/checkbox/index.d.ts +2 -0
  95. package/dist/components/ui/checkbox/index.js +2 -0
  96. package/dist/components/ui/command/command-dialog.svelte +42 -0
  97. package/dist/components/ui/command/command-dialog.svelte.d.ts +14 -0
  98. package/dist/components/ui/command/command-empty.svelte +17 -0
  99. package/dist/components/ui/command/command-empty.svelte.d.ts +4 -0
  100. package/dist/components/ui/command/command-group.svelte +33 -0
  101. package/dist/components/ui/command/command-group.svelte.d.ts +7 -0
  102. package/dist/components/ui/command/command-input.svelte +36 -0
  103. package/dist/components/ui/command/command-input.svelte.d.ts +4 -0
  104. package/dist/components/ui/command/command-item.svelte +27 -0
  105. package/dist/components/ui/command/command-item.svelte.d.ts +4 -0
  106. package/dist/components/ui/command/command-link-item.svelte +20 -0
  107. package/dist/components/ui/command/command-link-item.svelte.d.ts +4 -0
  108. package/dist/components/ui/command/command-list.svelte +20 -0
  109. package/dist/components/ui/command/command-list.svelte.d.ts +4 -0
  110. package/dist/components/ui/command/command-loading.svelte +7 -0
  111. package/dist/components/ui/command/command-loading.svelte.d.ts +4 -0
  112. package/dist/components/ui/command/command-separator.svelte +17 -0
  113. package/dist/components/ui/command/command-separator.svelte.d.ts +4 -0
  114. package/dist/components/ui/command/command-shortcut.svelte +23 -0
  115. package/dist/components/ui/command/command-shortcut.svelte.d.ts +5 -0
  116. package/dist/components/ui/command/command.svelte +28 -0
  117. package/dist/components/ui/command/command.svelte.d.ts +8 -0
  118. package/dist/components/ui/command/index.d.ts +12 -0
  119. package/dist/components/ui/command/index.js +14 -0
  120. package/dist/components/ui/command-palette/command-palette.svelte +94 -0
  121. package/dist/components/ui/command-palette/command-palette.svelte.d.ts +40 -0
  122. package/dist/components/ui/command-palette/index.d.ts +3 -0
  123. package/dist/components/ui/command-palette/index.js +2 -0
  124. package/dist/components/ui/context-column/context-column.svelte +56 -0
  125. package/dist/components/ui/context-column/context-column.svelte.d.ts +14 -0
  126. package/dist/components/ui/context-column/index.d.ts +2 -0
  127. package/dist/components/ui/context-column/index.js +2 -0
  128. package/dist/components/ui/data-table/data-table.svelte.d.ts +40 -0
  129. package/dist/components/ui/data-table/data-table.svelte.js +110 -0
  130. package/dist/components/ui/data-table/flex-render.svelte +40 -0
  131. package/dist/components/ui/data-table/flex-render.svelte.d.ts +33 -0
  132. package/dist/components/ui/data-table/index.d.ts +3 -0
  133. package/dist/components/ui/data-table/index.js +3 -0
  134. package/dist/components/ui/data-table/render-helpers.d.ts +90 -0
  135. package/dist/components/ui/data-table/render-helpers.js +99 -0
  136. package/dist/components/ui/data-table-tanstack/data-table-tanstack.svelte +247 -0
  137. package/dist/components/ui/data-table-tanstack/data-table-tanstack.svelte.d.ts +79 -0
  138. package/dist/components/ui/data-table-tanstack/index.d.ts +3 -0
  139. package/dist/components/ui/data-table-tanstack/index.js +2 -0
  140. package/dist/components/ui/data-table-toolbar/data-table-toolbar.svelte +102 -0
  141. package/dist/components/ui/data-table-toolbar/data-table-toolbar.svelte.d.ts +32 -0
  142. package/dist/components/ui/data-table-toolbar/index.d.ts +3 -0
  143. package/dist/components/ui/data-table-toolbar/index.js +2 -0
  144. package/dist/components/ui/detail-panel/detail-panel.svelte +95 -0
  145. package/dist/components/ui/detail-panel/detail-panel.svelte.d.ts +26 -0
  146. package/dist/components/ui/detail-panel/index.d.ts +2 -0
  147. package/dist/components/ui/detail-panel/index.js +2 -0
  148. package/dist/components/ui/dialog/dialog-close.svelte +11 -0
  149. package/dist/components/ui/dialog/dialog-close.svelte.d.ts +4 -0
  150. package/dist/components/ui/dialog/dialog-content.svelte +48 -0
  151. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +13 -0
  152. package/dist/components/ui/dialog/dialog-description.svelte +20 -0
  153. package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
  154. package/dist/components/ui/dialog/dialog-footer.svelte +35 -0
  155. package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +8 -0
  156. package/dist/components/ui/dialog/dialog-header.svelte +20 -0
  157. package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
  158. package/dist/components/ui/dialog/dialog-overlay.svelte +20 -0
  159. package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
  160. package/dist/components/ui/dialog/dialog-portal.svelte +7 -0
  161. package/dist/components/ui/dialog/dialog-portal.svelte.d.ts +3 -0
  162. package/dist/components/ui/dialog/dialog-title.svelte +17 -0
  163. package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
  164. package/dist/components/ui/dialog/dialog-trigger.svelte +11 -0
  165. package/dist/components/ui/dialog/dialog-trigger.svelte.d.ts +4 -0
  166. package/dist/components/ui/dialog/dialog.svelte +7 -0
  167. package/dist/components/ui/dialog/dialog.svelte.d.ts +3 -0
  168. package/dist/components/ui/dialog/index.d.ts +11 -0
  169. package/dist/components/ui/dialog/index.js +13 -0
  170. package/dist/components/ui/dialog-section/dialog-section.svelte +20 -0
  171. package/dist/components/ui/dialog-section/dialog-section.svelte.d.ts +8 -0
  172. package/dist/components/ui/dialog-section/index.d.ts +2 -0
  173. package/dist/components/ui/dialog-section/index.js +2 -0
  174. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  175. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +4 -0
  176. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
  177. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +9 -0
  178. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  179. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +10 -0
  180. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  181. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +8 -0
  182. package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  183. package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte.d.ts +4 -0
  184. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  185. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +8 -0
  186. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +27 -0
  187. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte.d.ts +8 -0
  188. package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  189. package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte.d.ts +3 -0
  190. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  191. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +4 -0
  192. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
  193. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +4 -0
  194. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  195. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte.d.ts +4 -0
  196. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
  197. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte.d.ts +5 -0
  198. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
  199. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +4 -0
  200. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  201. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +7 -0
  202. package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  203. package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte.d.ts +3 -0
  204. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  205. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +4 -0
  206. package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  207. package/dist/components/ui/dropdown-menu/dropdown-menu.svelte.d.ts +3 -0
  208. package/dist/components/ui/dropdown-menu/index.d.ts +18 -0
  209. package/dist/components/ui/dropdown-menu/index.js +18 -0
  210. package/dist/components/ui/empty-state/empty-state.svelte +44 -0
  211. package/dist/components/ui/empty-state/empty-state.svelte.d.ts +14 -0
  212. package/dist/components/ui/empty-state/index.d.ts +2 -0
  213. package/dist/components/ui/empty-state/index.js +2 -0
  214. package/dist/components/ui/error-state/error-state.svelte +52 -0
  215. package/dist/components/ui/error-state/error-state.svelte.d.ts +10 -0
  216. package/dist/components/ui/error-state/index.d.ts +2 -0
  217. package/dist/components/ui/error-state/index.js +2 -0
  218. package/dist/components/ui/info-tip/index.d.ts +2 -0
  219. package/dist/components/ui/info-tip/index.js +2 -0
  220. package/dist/components/ui/info-tip/info-tip.svelte +37 -0
  221. package/dist/components/ui/info-tip/info-tip.svelte.d.ts +10 -0
  222. package/dist/components/ui/input/index.d.ts +2 -0
  223. package/dist/components/ui/input/index.js +4 -0
  224. package/dist/components/ui/input/input.svelte +48 -0
  225. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  226. package/dist/components/ui/input-group/index.d.ts +7 -0
  227. package/dist/components/ui/input-group/index.js +9 -0
  228. package/dist/components/ui/input-group/input-group-addon.svelte +53 -0
  229. package/dist/components/ui/input-group/input-group-addon.svelte.d.ts +32 -0
  230. package/dist/components/ui/input-group/input-group-button.svelte +49 -0
  231. package/dist/components/ui/input-group/input-group-button.svelte.d.ts +32 -0
  232. package/dist/components/ui/input-group/input-group-input.svelte +23 -0
  233. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +11 -0
  234. package/dist/components/ui/input-group/input-group-text.svelte +22 -0
  235. package/dist/components/ui/input-group/input-group-text.svelte.d.ts +5 -0
  236. package/dist/components/ui/input-group/input-group-textarea.svelte +23 -0
  237. package/dist/components/ui/input-group/input-group-textarea.svelte.d.ts +3 -0
  238. package/dist/components/ui/input-group/input-group.svelte +24 -0
  239. package/dist/components/ui/input-group/input-group.svelte.d.ts +5 -0
  240. package/dist/components/ui/label/index.d.ts +2 -0
  241. package/dist/components/ui/label/index.js +4 -0
  242. package/dist/components/ui/label/label.svelte +20 -0
  243. package/dist/components/ui/label/label.svelte.d.ts +4 -0
  244. package/dist/components/ui/loading-state/index.d.ts +2 -0
  245. package/dist/components/ui/loading-state/index.js +2 -0
  246. package/dist/components/ui/loading-state/loading-state.svelte +28 -0
  247. package/dist/components/ui/loading-state/loading-state.svelte.d.ts +8 -0
  248. package/dist/components/ui/page-header/index.d.ts +2 -0
  249. package/dist/components/ui/page-header/index.js +2 -0
  250. package/dist/components/ui/page-header/page-header.svelte +100 -0
  251. package/dist/components/ui/page-header/page-header.svelte.d.ts +17 -0
  252. package/dist/components/ui/panel/index.d.ts +2 -0
  253. package/dist/components/ui/panel/index.js +2 -0
  254. package/dist/components/ui/panel/panel.svelte +54 -0
  255. package/dist/components/ui/panel/panel.svelte.d.ts +16 -0
  256. package/dist/components/ui/password-input/index.d.ts +2 -0
  257. package/dist/components/ui/password-input/index.js +4 -0
  258. package/dist/components/ui/password-input/password-input.svelte +48 -0
  259. package/dist/components/ui/password-input/password-input.svelte.d.ts +8 -0
  260. package/dist/components/ui/popover/index.d.ts +4 -0
  261. package/dist/components/ui/popover/index.js +6 -0
  262. package/dist/components/ui/popover/popover-content.svelte +45 -0
  263. package/dist/components/ui/popover/popover-content.svelte.d.ts +7 -0
  264. package/dist/components/ui/popover/popover-trigger.svelte +7 -0
  265. package/dist/components/ui/popover/popover-trigger.svelte.d.ts +4 -0
  266. package/dist/components/ui/popover/popover.svelte +7 -0
  267. package/dist/components/ui/popover/popover.svelte.d.ts +3 -0
  268. package/dist/components/ui/progress/index.d.ts +2 -0
  269. package/dist/components/ui/progress/index.js +4 -0
  270. package/dist/components/ui/progress/progress.svelte +30 -0
  271. package/dist/components/ui/progress/progress.svelte.d.ts +4 -0
  272. package/dist/components/ui/scorecard/index.d.ts +2 -0
  273. package/dist/components/ui/scorecard/index.js +2 -0
  274. package/dist/components/ui/scorecard/scorecard.svelte +35 -0
  275. package/dist/components/ui/scorecard/scorecard.svelte.d.ts +8 -0
  276. package/dist/components/ui/select/index.d.ts +12 -0
  277. package/dist/components/ui/select/index.js +14 -0
  278. package/dist/components/ui/select/select-content.svelte +45 -0
  279. package/dist/components/ui/select/select-content.svelte.d.ts +11 -0
  280. package/dist/components/ui/select/select-group-heading.svelte +21 -0
  281. package/dist/components/ui/select/select-group-heading.svelte.d.ts +10 -0
  282. package/dist/components/ui/select/select-group.svelte +17 -0
  283. package/dist/components/ui/select/select-group.svelte.d.ts +4 -0
  284. package/dist/components/ui/select/select-item.svelte +38 -0
  285. package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
  286. package/dist/components/ui/select/select-label.svelte +20 -0
  287. package/dist/components/ui/select/select-label.svelte.d.ts +6 -0
  288. package/dist/components/ui/select/select-portal.svelte +7 -0
  289. package/dist/components/ui/select/select-portal.svelte.d.ts +3 -0
  290. package/dist/components/ui/select/select-scroll-down-button.svelte +23 -0
  291. package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
  292. package/dist/components/ui/select/select-scroll-up-button.svelte +23 -0
  293. package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
  294. package/dist/components/ui/select/select-separator.svelte +18 -0
  295. package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
  296. package/dist/components/ui/select/select-trigger.svelte +29 -0
  297. package/dist/components/ui/select/select-trigger.svelte.d.ts +8 -0
  298. package/dist/components/ui/select/select.svelte +11 -0
  299. package/dist/components/ui/select/select.svelte.d.ts +3 -0
  300. package/dist/components/ui/separator/index.d.ts +2 -0
  301. package/dist/components/ui/separator/index.js +4 -0
  302. package/dist/components/ui/separator/separator.svelte +23 -0
  303. package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
  304. package/dist/components/ui/skeleton/index.d.ts +2 -0
  305. package/dist/components/ui/skeleton/index.js +4 -0
  306. package/dist/components/ui/skeleton/skeleton.svelte +17 -0
  307. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
  308. package/dist/components/ui/sonner/index.d.ts +1 -0
  309. package/dist/components/ui/sonner/index.js +1 -0
  310. package/dist/components/ui/sonner/sonner.svelte +34 -0
  311. package/dist/components/ui/sonner/sonner.svelte.d.ts +4 -0
  312. package/dist/components/ui/sparkline/index.d.ts +3 -0
  313. package/dist/components/ui/sparkline/index.js +2 -0
  314. package/dist/components/ui/sparkline/sparkline.svelte +90 -0
  315. package/dist/components/ui/sparkline/sparkline.svelte.d.ts +16 -0
  316. package/dist/components/ui/stat-card/index.d.ts +2 -0
  317. package/dist/components/ui/stat-card/index.js +2 -0
  318. package/dist/components/ui/stat-card/stat-card.svelte +77 -0
  319. package/dist/components/ui/stat-card/stat-card.svelte.d.ts +34 -0
  320. package/dist/components/ui/stat-list/index.d.ts +3 -0
  321. package/dist/components/ui/stat-list/index.js +2 -0
  322. package/dist/components/ui/stat-list/stat-list.svelte +80 -0
  323. package/dist/components/ui/stat-list/stat-list.svelte.d.ts +23 -0
  324. package/dist/components/ui/status/index.d.ts +1 -0
  325. package/dist/components/ui/status/index.js +1 -0
  326. package/dist/components/ui/status-badge/index.d.ts +3 -0
  327. package/dist/components/ui/status-badge/index.js +2 -0
  328. package/dist/components/ui/status-badge/status-badge.svelte +53 -0
  329. package/dist/components/ui/status-badge/status-badge.svelte.d.ts +39 -0
  330. package/dist/components/ui/switch/index.d.ts +2 -0
  331. package/dist/components/ui/switch/index.js +4 -0
  332. package/dist/components/ui/switch/switch.svelte +39 -0
  333. package/dist/components/ui/switch/switch.svelte.d.ts +4 -0
  334. package/dist/components/ui/table/index.d.ts +10 -0
  335. package/dist/components/ui/table/index.js +12 -0
  336. package/dist/components/ui/table/table-body.svelte +20 -0
  337. package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
  338. package/dist/components/ui/table/table-caption.svelte +20 -0
  339. package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
  340. package/dist/components/ui/table/table-cell.svelte +20 -0
  341. package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
  342. package/dist/components/ui/table/table-classes.d.ts +15 -0
  343. package/dist/components/ui/table/table-classes.js +15 -0
  344. package/dist/components/ui/table/table-footer.svelte +20 -0
  345. package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
  346. package/dist/components/ui/table/table-head.svelte +23 -0
  347. package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
  348. package/dist/components/ui/table/table-header.svelte +20 -0
  349. package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
  350. package/dist/components/ui/table/table-row.svelte +23 -0
  351. package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
  352. package/dist/components/ui/table/table.svelte +37 -0
  353. package/dist/components/ui/table/table.svelte.d.ts +20 -0
  354. package/dist/components/ui/tabs/index.d.ts +5 -0
  355. package/dist/components/ui/tabs/index.js +7 -0
  356. package/dist/components/ui/tabs/tabs-content.svelte +13 -0
  357. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
  358. package/dist/components/ui/tabs/tabs-list.svelte +16 -0
  359. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
  360. package/dist/components/ui/tabs/tabs-trigger.svelte +16 -0
  361. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  362. package/dist/components/ui/tabs/tabs.svelte +19 -0
  363. package/dist/components/ui/tabs/tabs.svelte.d.ts +4 -0
  364. package/dist/components/ui/textarea/index.d.ts +2 -0
  365. package/dist/components/ui/textarea/index.js +4 -0
  366. package/dist/components/ui/textarea/textarea.svelte +22 -0
  367. package/dist/components/ui/textarea/textarea.svelte.d.ts +5 -0
  368. package/dist/components/ui/tile-grid/index.d.ts +2 -0
  369. package/dist/components/ui/tile-grid/index.js +2 -0
  370. package/dist/components/ui/tile-grid/tile-grid.svelte +66 -0
  371. package/dist/components/ui/tile-grid/tile-grid.svelte.d.ts +43 -0
  372. package/dist/components/ui/tooltip/index.d.ts +6 -0
  373. package/dist/components/ui/tooltip/index.js +8 -0
  374. package/dist/components/ui/tooltip/tooltip-content.svelte +52 -0
  375. package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +11 -0
  376. package/dist/components/ui/tooltip/tooltip-portal.svelte +7 -0
  377. package/dist/components/ui/tooltip/tooltip-portal.svelte.d.ts +4 -0
  378. package/dist/components/ui/tooltip/tooltip-provider.svelte +7 -0
  379. package/dist/components/ui/tooltip/tooltip-provider.svelte.d.ts +4 -0
  380. package/dist/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  381. package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
  382. package/dist/components/ui/tooltip/tooltip.svelte +7 -0
  383. package/dist/components/ui/tooltip/tooltip.svelte.d.ts +4 -0
  384. package/dist/styles.css +961 -0
  385. package/dist/utils.d.ts +12 -0
  386. package/dist/utils.js +5 -0
  387. package/package.json +81 -0
package/README.md ADDED
@@ -0,0 +1,624 @@
1
+ # @poodle64/ui
2
+
3
+ Household shared shadcn-svelte component primitives (bits-ui), extracted from
4
+ the estate's most conformant consuming app's frontend — the best-looking,
5
+ most battle-tested implementation of each primitive — plus `alert`/`popover`/
6
+ `progress`/`tabs` from the first app that migrated onto this package (WP-51
7
+ Lane WP) and needed them.
8
+
9
+ `bits-ui` is a required peerDependency: components share compound-component
10
+ context across the package, so a duplicated `bits-ui` instance is a real
11
+ functional bug (mismatched types at best). `mode-watcher` and `svelte-sonner`
12
+ are optional peers (`peerDependenciesMeta`), needed only if the consuming app
13
+ uses `@poodle64/ui/sonner` (a single dark-mode store for `mode-watcher`, one
14
+ toast queue for `svelte-sonner`'s `toast()` + `Toaster` pair — a duplicated
15
+ instance there means a `Toaster` that never sees the app's own `toast()`
16
+ calls). Declare whichever peers you use directly in your own `package.json`:
17
+ pnpm auto-installs missing peers, but an explicit dependency is what lets
18
+ Renovate track the version and `pnpm ls` show it.
19
+
20
+ Every app previously vendored its own copy of these primitives and restyled them
21
+ through its `@poodle64/design-tokens` alias layer. That let apps differ by palette,
22
+ but a fix (an accessibility bug, a focus-trap issue) had to be applied once per app.
23
+ This package is the same restyling mechanism: components consume shadcn's standard
24
+ CSS variable names (`bg-primary`, `text-foreground`, `--radius`, …), and both the
25
+ component code and the mapping those names resolve through now live once, here.
26
+ Apps differ by palette and nothing else. Override `--ds-color-*` and the whole
27
+ shadcn surface follows; a consuming app writes no alias layer of its own.
28
+
29
+ ## What is here
30
+
31
+ ```text
32
+ src/lib/
33
+ utils.ts cn() (clsx + tailwind-merge) and the shared TS helper types
34
+ styles.css the component stylesheet: scale keys, .ds-edge, .ds-chip/.ds-dot,
35
+ the dialogue-section divider rule
36
+ components/ui/ one directory per component: the shadcn-svelte primitives
37
+ (bits-ui behaviour + shadcn markup/variants) and the composed
38
+ page-chrome components built on top of them
39
+ dist/ generated by `pnpm build` (@sveltejs/package); never edit
40
+ ```
41
+
42
+ **Primitives** (26 — battle-tested implementations pulled from whichever app had
43
+ them first, not an invented "ideal" list): `alert`, `alert-dialog`, `avatar`,
44
+ `badge`, `button`, `card`, `checkbox`, `command`, `data-table`, `dialog`,
45
+ `dropdown-menu`, `input`, `input-group`, `label`, `password-input`, `popover`,
46
+ `progress`, `select`, `separator`, `skeleton`, `sonner`, `switch`, `table`,
47
+ `tabs`, `textarea`, `tooltip`.
48
+
49
+ `avatar` is root, image and fallback only; the group and badge variants in the
50
+ upstream registry had no consumer in any surveyed app. It is here because
51
+ `AppShell`'s `identity` slot needs one, and a consumer filling a slot the shell
52
+ itself defines should not have to keep a private copy of a primitive to do it.
53
+
54
+ Not yet included: `sheet` — no app's `ui/` has vendored it yet. Add it here
55
+ (`pnpm dlx shadcn-svelte@latest add <name>` inside `packages/ui`, or hand-port from
56
+ a sibling app once one adopts it) the first time a converging app actually needs it;
57
+ do not invent it speculatively.
58
+
59
+ **Deliberately not here: `chart` and `form`.** Two apps keep local copies of each,
60
+ and both stay local for reasons that are about coupling rather than effort.
61
+
62
+ `form` in the upstream registry is a Formsnap wrapper over Superforms. Adding it
63
+ would make a form library a peer of the whole design system and decide, for every
64
+ app, how validation and submission work — which is an application-architecture
65
+ choice, not a design one. An app that uses Superforms already gets its markup from
66
+ `input`, `label`, `input-group` and `button` here; what it keeps locally is the
67
+ binding layer, which is where it belongs.
68
+
69
+ `chart` is a LayerChart wrapper, and it needs the `chart-1`…`chart-5` colour keys
70
+ this stylesheet deliberately does not register — chart hue is the one part of the
71
+ shadcn surface the estate genuinely disagrees on, and registering it here would
72
+ claim a key no app can then override without a stylesheet-order coin toss (see
73
+ the one-owner-per-key note below). It becomes a candidate the day two apps want
74
+ the same chart palette; until then a shared chart component would be a shared
75
+ disagreement.
76
+
77
+ **Composed components** (20). Primitives are not what makes an app look like an
78
+ app — the page chrome is. These are the cross-cutting surfaces every route
79
+ composes from, so a household app gets its layout language from the package
80
+ rather than rebuilding it:
81
+
82
+ | Import | What it is |
83
+ | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
84
+ | `page-header` | The only page-title pattern: optional `breadcrumbs` snippet, eyebrow, an optional title, one clamped subtitle, an `info` tooltip, an `actions` slot. Omit `title` for a header that is a breadcrumb bar. |
85
+ | `panel` | The generic titled card: optional icon, subtitle and trailing actions over a body that can opt out of padding. |
86
+ | `detail-panel` | The entity-detail surface: header with icon/eyebrow/title/`StatusBadge`/close, scrollable body, footer of actions. |
87
+ | `context-column` | The persistent right-hand column: a standing `StatList` plus an optional detail that flows in on select. |
88
+ | `app-dialog` | The dialogue frame: titled header, scrollable body, footer action bar, five sizes (`xs`…`xl`), and an `onOpenChange` for the dismissals the caller did not drive. |
89
+ | `dialog-section` | One section of a dialogue body; adjacent sections are divided automatically. |
90
+ | `stat-card` | A single metric that earns its space (label, value, unit, sub, status dot, and `valueTone` to colour the figure itself). |
91
+ | `stat-list` | A route's low-context integers as a label→value list. Zero-aware: `muted` keeps a healthy zero quiet. |
92
+ | `arc-gauge` | A radial capacity/percentage ring for a single 0–100 metric, in a footprint too compact for a `stat-card`. |
93
+ | `bar-row` | A labelled horizontal bar with a trailing tabular value, for a ranked list (usage, rank, token burn). |
94
+ | `scorecard` | A compact 0/1/2 dot-row health strip for several independent checks read at a glance. |
95
+ | `sparkline` | An inline multi-series area+line trend for a row or card with room for a trend but not a full chart. |
96
+ | `status` / `status-badge` | The fixed five-state vocabulary (`success \| warning \| error \| info \| neutral`) and the one state chip, with `pulse` for a state still in motion and `class` for placement. `status-badge` alone also accepts `'primary'`, a brand-emphasis extension outside the shared vocabulary — `stat-card`, `stat-list` and `data-table-toolbar` never see it. |
97
+ | `empty-state` / `error-state` / `loading-state` | The shared blank, error and loading surfaces. Never hand-roll one. |
98
+ | `info-tip` | One tooltip pattern: a small info trigger, or wrap an existing affordance as children. |
99
+ | `data-table-toolbar` | Search field plus filter-chip groups for a TanStack table. Owns no state; fires callbacks. |
100
+ | `data-table-tanstack` | The TanStack-backed table: global search, column filters, master-detail row select, opt-in bulk selection, responsive column hiding, a first-class empty branch. |
101
+
102
+ **The application shell** (`app-shell`, `command-palette`). Page chrome is not
103
+ what makes an app feel like an app either — the shell is. Five household
104
+ frontends were surveyed before this API was settled, and between them they had
105
+ built five shells: a rail-plus-drawer, an eleven-file collapsible sidebar tree
106
+ under its own top bar, and three header-only bars that each answered the mobile
107
+ question differently. They agreed on almost nothing structurally while trying to
108
+ be the same thing.
109
+
110
+ There is now exactly one shell shape, no prop to choose a different one.
111
+ Operator ruling, 31/07/2026: every household app renders the same composition,
112
+ a permanent side rail on `md`+ (an overlay drawer below it) carrying primary
113
+ navigation, brand and the collapse toggle, **plus** a real top navbar (search,
114
+ leading/trailing slots, theme toggle, identity), always both together, with
115
+ content that is always full-width. Apps do not choose variants, content modes,
116
+ or spacing.
117
+
118
+ Everything the apps otherwise differed on turned out to be a slot, not a variant.
119
+ The package therefore imports no app store, no app route and no app brand:
120
+
121
+ ```svelte
122
+ <script lang="ts">
123
+ import { page } from '$app/state';
124
+ import { goto } from '$app/navigation';
125
+ import AppShell, { type NavGroup } from '@poodle64/ui/app-shell';
126
+ import CommandPalette from '@poodle64/ui/command-palette';
127
+ import { nav } from '$lib/config/navigation'; // typed as NavGroup[]
128
+
129
+ let paletteOpen = $state(false);
130
+ </script>
131
+
132
+ <AppShell {nav} brandTitle="Console" currentPath={page.url.pathname}
133
+ onSearch={() => (paletteOpen = true)}>
134
+ {@render children()}
135
+ </AppShell>
136
+ <CommandPalette bind:open={paletteOpen} {nav} onNavigate={goto} />
137
+ ```
138
+
139
+ That is the whole minimum. Everything below is optional.
140
+
141
+ | Prop | Purpose |
142
+ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
143
+ | `nav` | Bare `NavItem`s, `NavGroup`s, or a mix. Consecutive bare items collapse into one run; an emptied group renders nothing. |
144
+ | `currentPath` | Active state, and closing the mobile nav on navigation. |
145
+ | `collapsible`, `collapsed` | An icon-only rail collapse toggle whose state binds out so an app can persist it. |
146
+ | `brand` / `brandTitle` + `brandMark` / `homeHref` | Full control of the lockup, or the wordmark-plus-mark shorthand. |
147
+ | `identity` | The signed-in surface. Rendered once, at the end of the top bar. |
148
+ | `context`, `actions` | Leading and trailing top-bar slots: a store/tenant switcher, app-level action buttons. |
149
+ | `banner` | Full-width region under the bar: reconnect notices, trial warnings. |
150
+ | `sidebar` | A secondary, route-scoped column between the nav and the page body. |
151
+ | `onSearch`, `searchLabel`, `searchShortcut` | Provide `onSearch` to render the search affordance at all. |
152
+ | `themeToggle`, `onToggleTheme` | Defaults to `mode-watcher`. Set `themeToggle={false}` when the app puts theming inside its own user menu. |
153
+ | `measure` | How wide the page body may get, from a named scale. Defaults to `full` (no cap). |
154
+ | `texture` | The house atmosphere on the content region. Defaults to `none`. |
155
+ | `padded`, `mainClass` | Padding, and extra classes on the scrolling content container. |
156
+
157
+ ### Nested navigation
158
+
159
+ A section with its own navigation puts it on the item, as `children`, and the
160
+ rail discloses it in place:
161
+
162
+ ```ts
163
+ {
164
+ label: 'Education',
165
+ href: '/education',
166
+ icon: BookOpen,
167
+ children: [
168
+ { label: 'Open architecture', href: '/education/open-architecture' },
169
+ { label: 'Autonomy', href: '/education/autonomy' }
170
+ ]
171
+ }
172
+ ```
173
+
174
+ That is the whole API. An item with no `children` renders and behaves exactly as
175
+ it did before the field existed.
176
+
177
+ It exists because the alternative was two left-hand columns: an app whose
178
+ sections have inner navigation had nowhere to put it in the rail, so it put
179
+ modules in `nav` and the current section's pages in `sidebar`. The other way out
180
+ (modules along the top bar, rail for the current module) was rejected on mobile:
181
+ it leaves two navigation surfaces that both need collapsing and both want the
182
+ same hamburger. One nested tree collapses to one drawer.
183
+
184
+ The behaviour, and why:
185
+
186
+ - **The parent stays a destination.** Its label navigates; a separate chevron
187
+ expands. Folding both into the link cannot be made honest, since
188
+ `aria-expanded` on something that navigates away announces a state the user
189
+ never observes; and making the label expand-only would silently change what
190
+ `href` means the day an app adds children to an item that already had one.
191
+ - **A group holding the current page is open**, on first paint, with nothing
192
+ clicked. A rail that will not show you where you are is the defect this
193
+ feature exists to remove.
194
+ - **A deliberate toggle wins, and keeps winning** for the life of the shell,
195
+ which in a SvelteKit layout is the session. It overrides the path default rather than
196
+ replacing it, so an untouched group still opens as you walk into it while a
197
+ group you made a decision about keeps your decision. Nothing is written to
198
+ storage: a shared package writing to a fixed key would collide with the app's
199
+ own, and with itself on a page rendering two navs.
200
+ - **One level, enforced by the type.** `children` holds `NavChildItem`, which is
201
+ `NavItem` minus `children`, so a second nesting is a compile error where the
202
+ nav is authored. The rail is 15.5rem and every level costs an indent; by depth
203
+ three the label has less room than the chevron.
204
+ - **A collapsed rail renders no tree.** 3.5rem of icons cannot hold one, and a
205
+ hover flyout would be a second interaction surface with its own positioning,
206
+ touch and focus story. The parent stays an icon-only link to its own page; the
207
+ way back is the Collapse control the user just used.
208
+ - **The drawer renders the same tree.** It is the same element as the rail, and
209
+ it is never collapsed.
210
+
211
+ What this does not absorb, and deliberately: a `children` list is flat and one
212
+ deep, so a section whose own navigation is itself grouped under sub-headings, or
213
+ whose rows disclose a third level, does not lift into the rail whole. The rail is
214
+ not the place to fix that. At 15.5rem a third level leaves roughly 128px for the
215
+ label, which is about fifteen characters. A section that deep keeps its deepest
216
+ level on its own page, where there is width for it, and the `sidebar` snippet
217
+ stays the right home for a column that is not `NavItem`-shaped at all: a document
218
+ tree, a table of contents, a filter panel.
219
+
220
+ `NavItem` / `NavGroup` are exported so an app types its own config against them.
221
+ They carry **no notion of who may see an item**: two surveyed apps gate
222
+ navigation on admin or per-module permission, and both do it with their own auth
223
+ store. Apps filter before they pass (`nav.filter((i) => !i.adminOnly || user.isAdmin)`),
224
+ and that is what keeps this package free of the auth coupling that made the best
225
+ shell in the estate unliftable in the first place.
226
+
227
+ `currentPath` is a prop rather than a `$app/state` import for the same reason:
228
+ this package is built with `svelte-package` and has no SvelteKit runtime, so
229
+ importing it would make SvelteKit a hard peer and make the shell untestable
230
+ outside a running app.
231
+
232
+ `CommandPalette` ships beside the shell because it carried the identical
233
+ coupling to a hardcoded navigation module; leaving it behind would have stranded
234
+ the shell's search affordance. It binds ⌘K / Ctrl-K itself (`shortcut={false}` to
235
+ opt out) and takes a `children` snippet for app-specific command groups beneath
236
+ the navigation group.
237
+
238
+ The shell paints its chrome from `--ds-shell-chrome`, registered as the `shell`
239
+ colour key. It is deliberately **not** `sidebar`: those keys stay the app's to
240
+ define, and one-owner-per-key is what keeps an override from depending on
241
+ stylesheet order. An app that already has a chrome hue points it there in one
242
+ line, `:root { --ds-shell-chrome: var(--sidebar); }`, and `--ds-shell-rail-width`
243
+ retunes the rail.
244
+
245
+ Chrome **ink** follows the same pattern, through `--ds-shell-chrome-foreground`
246
+ and `--ds-shell-chrome-muted-foreground`. Both default to the page's own
247
+ foreground tokens, so nothing moves until an app asks; set them to invert the
248
+ whole chrome — rail, bar, every chrome control and every nav row — against the
249
+ palette:
250
+
251
+ ```css
252
+ :root {
253
+ --ds-shell-chrome: var(--ds-color-primary);
254
+ --ds-shell-chrome-foreground: var(--ds-color-primary-foreground);
255
+ --ds-shell-chrome-muted-foreground: color-mix(
256
+ in oklch,
257
+ var(--ds-color-primary-foreground) 70%,
258
+ transparent
259
+ );
260
+ }
261
+ ```
262
+
263
+ `AppNav` used outside the chrome (the `sidebar` slot's own column) keeps the
264
+ page's ink, so inverting the rail does not drag a secondary nav with it.
265
+
266
+ ### The content measure
267
+
268
+ How wide the page body may get, chosen once in the layout from a scale of four:
269
+
270
+ ```svelte
271
+ <AppShell {nav} currentPath={page.url.pathname} measure="page">
272
+ {@render children()}
273
+ </AppShell>
274
+ ```
275
+
276
+ | Value | Width | Reach for it when the page is |
277
+ | ------- | -------- | -------------------------------------------------------------------- |
278
+ | `prose` | `72ch` | long-form running text: documentation, an article, a policy, a guide |
279
+ | `page` | `80rem` | an everyday page: a form, a detail view, settings, a wizard |
280
+ | `wide` | `120rem` | an index or a dashboard: card grids, tables, charts, board columns |
281
+ | `full` | no cap | a canvas that should use the whole panel: a map, a diagram, a board |
282
+
283
+ `full` is the default, so a shell that does not mention `measure` renders exactly
284
+ as it did before the prop existed — verified, not assumed: the content region's
285
+ markup and geometry were diffed against the pre-change build across three
286
+ surfaces at three viewports and are identical on every field (`harness/drive.md`).
287
+ Adoption is deliberate, one app at a time.
288
+
289
+ It exists because "content is always full-width" did not remove the width
290
+ decision, it pushed it into every page. Surveyed at 2560px, one consumer had six
291
+ distinct caps across nine routes — each a hand-written `mx-auto max-w-*` at the
292
+ top of a `+page.svelte`, none wrong on its own, no two agreeing — using between
293
+ 15% and 79% of the width available. Across the estate that is 42 hand-rolled caps
294
+ in 34 page files, spanning eight different `max-w-*` values. A scale with four
295
+ names cannot drift like that.
296
+
297
+ **Why `prose` is a tier at all.** Because a shared measure that let running text
298
+ span a large display would be worse than what it replaces, and worse everywhere
299
+ at once. Measured in Chromium at 2560px: the same paragraph runs to **311
300
+ characters per line** uncapped, against an accepted band of 45–90 for continuous
301
+ text. `prose` puts it at 80. It is stated in `ch` rather than `rem` deliberately —
302
+ a reading measure is a count of characters, not a length, so it has to track
303
+ whatever face and size the app actually set.
304
+
305
+ Every tier is a CSS custom property, so an app retunes one without waiting on a
306
+ release of this package:
307
+
308
+ ```css
309
+ :root {
310
+ --ds-shell-measure-prose: 66ch;
311
+ --ds-shell-measure-page: 72rem;
312
+ --ds-shell-measure-wide: 100rem;
313
+ }
314
+ ```
315
+
316
+ Two things worth knowing before you set it:
317
+
318
+ - **The cap is a ceiling, never a floor.** `page` caps at 80rem; a 1440px laptop
319
+ has less than that available, so nothing changes there. A measure never narrows
320
+ a window that was already narrower than the tier.
321
+ - **`prose` moves when the font does.** A webfont arriving after first paint
322
+ reflows the column, because `ch` is measured in the face that actually
323
+ rendered. That is the tier doing its job rather than a bug; `page` and `wide`
324
+ are lengths and never move.
325
+ - **The cap is the border box, so `padded` spends its padding inside it.**
326
+ `measure="page"` with the default padding is 80rem of box holding 78rem of
327
+ content, which is the same arithmetic as the `mx-auto max-w-4xl px-6` idiom it
328
+ replaces. An app running `padded={false}` and its own page padding gets the cap
329
+ flush, which is usually what it wanted.
330
+
331
+ Set it in the layout, not the page. A page reaching for `measure` is the habit
332
+ this replaces; a route group that genuinely differs (a docs section inside an
333
+ app of dashboards) gets its own layout, which is where a shared decision belongs.
334
+
335
+ ### The content texture
336
+
337
+ The house atmosphere — a faint dot-grid floor with a soft accent vignette in the
338
+ top-right — painted once, by the shell, on the region that scrolls:
339
+
340
+ ```svelte
341
+ <AppShell {nav} currentPath={page.url.pathname} texture="grid">
342
+ {@render children()}
343
+ </AppShell>
344
+ ```
345
+
346
+ | Value | What it paints | Reach for it when |
347
+ | ------ | ------------------------------------------------------- | ------------------------------------------------------------------------ |
348
+ | `grid` | a dot-grid floor plus one accent vignette in the corner | the app wants the house "instrument-grade" surface rather than flat page |
349
+ | `none` | nothing at all | the default |
350
+
351
+ `none` is the default, so a shell that does not mention `texture` renders exactly
352
+ as it did before the prop existed. Verified, not assumed, and reproducibly so:
353
+
354
+ ```sh
355
+ node harness/additivity.mjs ui-v2026.8.3
356
+ # 15 surface/viewport pairs, 105 compared fields (including the screenshot hash)
357
+ # IDENTICAL on every field and every pixel — the change is additive.
358
+ ```
359
+
360
+ `harness/additivity.mjs` builds the package at any base ref in a throwaway git
361
+ worktree, renders the five surfaces an existing consumer already has at 2560px,
362
+ 1440px and 360px, and diffs the markup, both attribute sets, the computed box and
363
+ background properties, the geometry and the rendered pixels. Adoption is
364
+ deliberate, one app at a time.
365
+
366
+ **Why it lives on the shell rather than being a class an app applies.** Because
367
+ that is the entire defect it fixes. Two apps had built this same picture
368
+ separately, under two names, in two `app.css` files, and one of them shipped it
369
+ as a helper class routes opt into, which reached four routes out of about
370
+ fifteen. Whether a given page wore the house atmosphere therefore came down to
371
+ which pages someone had happened to touch, and no amount of care at the call site
372
+ fixes that: a texture an app has to _remember_ to apply is not a texture, it is a
373
+ coin toss with a stylesheet. On the shell there is one place to say it and no
374
+ per-route decision left to get wrong.
375
+
376
+ It is one texture rather than a menu. The two apps wanted the same picture and
377
+ differed only on values: how dark the dots are, how the vignette is tinted, how
378
+ far apart the dots sit. Those are custom properties, retuned in one
379
+ declaration without waiting on a release of this package:
380
+
381
+ ```css
382
+ :root {
383
+ --ds-shell-texture-grid-ink: oklch(0.68 0.02 250 / 0.5);
384
+ --ds-shell-texture-vignette-ink: color-mix(in oklch, var(--primary) 8%, transparent);
385
+ --ds-shell-texture-grid-pitch: 24px;
386
+ --ds-shell-texture-vignette-height: 40rem;
387
+ --ds-shell-texture-vignette-at: 15% -10%;
388
+ }
389
+ ```
390
+
391
+ The defaults derive from the app's own `--foreground` and `--primary`, so the
392
+ grid inverts between light and dark with no per-mode override and the vignette
393
+ carries the app's brand rather than a colour this package picked. A plain wash
394
+ instead of a grid is the same declaration with `--ds-shell-texture-grid-ink: transparent`.
395
+
396
+ Three things worth knowing before you set it:
397
+
398
+ - **It is a background, not a layer.** Both apps that built this first reached for
399
+ an absolutely positioned `::before` inside the scroller, which is subtly wrong
400
+ three ways: at `z-index: auto` a positioned box paints _above_ non-positioned
401
+ content, so the atmosphere tints the page instead of sitting under it (invisible
402
+ only because it is faint); `z-index: -1` trades that for sinking behind an
403
+ ancestor's background; and it has to be excused from hit-testing by hand. A
404
+ background cannot be hit-tested, always paints beneath every descendant, adds no
405
+ box to the flex column and opens no stacking context. Proved rather than
406
+ claimed: an opaque card photographs byte-identically with the texture on and
407
+ off, while the floor beside it does not.
408
+ - **It does not print.** A 30px dot grid prints as banding and a vignette as a
409
+ corner smudge, so on paper the texture is suppressed and the content region goes
410
+ white. That lives here rather than in each app's print rules; it is the same
411
+ copy both apps had already written for themselves. `html`/`body` stay the app's
412
+ to decide.
413
+ - **It travels with the content.** A scroll container's background defaults to
414
+ being pinned to its own box, which would leave the dots hanging motionless while
415
+ the page slid over them; `background-attachment: local` makes it the floor the
416
+ page sits on. Driven by photographing bare floor either side of a half-pitch
417
+ scroll, with the frozen behaviour forced on as a control.
418
+ - **The corner is a knob because a gradient position is physical.** There is no
419
+ logical form of `at 85%`, so an RTL app wanting the glow at the reading-start
420
+ corner sets `--ds-shell-texture-vignette-at`. Driven at `dir=rtl`, at a 24px
421
+ root size, and under `forced-colors: active`; none of those move the texture on
422
+ their own.
423
+
424
+ Set it in the layout, once. That is the whole point of the prop.
425
+
426
+ ### The active nav row: primary is a fill, never an ink
427
+
428
+ The active row is marked with a 12% `--ds-color-primary` tint, a primary edge
429
+ bar on the rail, a weight step to 500, and `aria-current="page"`.
430
+ Its **label** is painted in the chrome's own foreground, not in the brand colour.
431
+
432
+ That is deliberate. `--ds-color-primary` is the one token every app overrides,
433
+ and the only constraint stated where an app picks it is that it clear AA against
434
+ its own `-foreground` pair: the fill case. Painting it as ink on the chrome
435
+ would impose a second, stricter requirement that nothing states and that
436
+ constrains a brand hue far more tightly. Measured in Chromium, a warm amber that
437
+ is 7.69:1 as a fill was **1.90:1** as an active nav label, and a saturated blue
438
+ that is 5:1 as a fill was **2.87:1**. Both palettes were entirely sanctioned. So
439
+ the shell stops asking a colour it does not control to be legible text, and no
440
+ app is ever told to repaint its brand to make the shared shell readable.
441
+
442
+ An app whose primary genuinely is legible as ink on its chrome can put it back,
443
+ and owns the contrast in doing so:
444
+
445
+ ```css
446
+ :root {
447
+ --ds-nav-ink-active: var(--ds-color-primary);
448
+ }
449
+ ```
450
+
451
+ Check that at ≥4.5:1 against `--ds-color-surface-1` (the chrome), not against
452
+ `--ds-color-background`; they are different surfaces, and the chrome is the
453
+ darker of the two. `harness/drive.mjs` gates the default path across three
454
+ palettes in both themes on every run.
455
+
456
+ ### Measuring the content region
457
+
458
+ About overflow, not width — for how wide the body is allowed to get, see
459
+ [The content measure](#the-content-measure) above.
460
+
461
+ `<main>` is the scrolling content region and carries
462
+ `data-slot="app-shell-content"`. A consuming app's "no horizontal overflow" check
463
+ must measure **that element**, not the document:
464
+
465
+ ```ts
466
+ const region = page.locator('[data-slot="app-shell-content"]');
467
+ expect(await region.evaluate((el) => el.scrollWidth - el.clientWidth)).toBeLessThanOrEqual(0);
468
+ ```
469
+
470
+ `overflow-y: auto` makes `overflow-x` compute to `auto` too, so a wide child's
471
+ excess ends up in this box and never reaches
472
+ `document.documentElement.scrollWidth` — the number nearly every app's overflow
473
+ test reads, and one that cannot move. A page can scroll sideways on a phone with
474
+ that suite green throughout.
475
+
476
+ The shell constrains its own boxes, but a child wider than the region has to
477
+ carry its own scroller: `@poodle64/ui/table` does (`data-slot="table-container"`,
478
+ and `containerClass` if you need to cap its height for a sticky header), and a
479
+ `<pre>` or an unbreakable string needs one from the page.
480
+
481
+ The table is the TanStack-shaped pair the household actually runs, not a
482
+ `rows`/`columns` config API. The page owns the `Table` instance (built with
483
+ `createSvelteTable` from `@poodle64/ui/data-table`) and passes it in; the
484
+ component owns how it looks and how a row is picked. For a small static table
485
+ that does not earn a table instance, `@poodle64/ui/table` also exports the
486
+ `TH_CLASS` / `TD_CLASS` / `TH_HIDDEN_UNTIL_XL` constants for the raw-`<table>`
487
+ idiom.
488
+
489
+ A column's `meta.class` reaches both its `<th>` and its `<td>`; `meta.headClass`
490
+ / `meta.cellClass` reach one cell only, additive on top of `class` (the
491
+ cell-specific slot wins on a conflicting Tailwind utility). Reach for the split
492
+ whenever a class must differ between the two — a truncating column needs
493
+ `max-w-0` on the `<td>` to ellipsis instead of pushing every other column out,
494
+ but the same class on the `<th>` collapses the heading over its neighbour:
495
+
496
+ ```ts
497
+ { accessorKey: 'filename', header: 'Document', meta: { class: 'w-full', cellClass: 'max-w-0' } }
498
+ ```
499
+
500
+ ## Consuming the package
501
+
502
+ Published to public npm under the `@poodle64` scope, same as `@poodle64/design-tokens`
503
+ — no registry config, no `.npmrc`, and no auth token needed to install.
504
+
505
+ ```bash
506
+ pnpm add @poodle64/ui @poodle64/design-tokens
507
+ ```
508
+
509
+ ```svelte
510
+ <script lang="ts">
511
+ import { Button } from '@poodle64/ui/button';
512
+ import * as Dialog from '@poodle64/ui/dialog';
513
+ import PageHeader from '@poodle64/ui/page-header';
514
+ import StatusBadge from '@poodle64/ui/status-badge';
515
+ import type { Status } from '@poodle64/ui/status';
516
+ </script>
517
+ ```
518
+
519
+ Composed components export both a default and a named binding, so either import
520
+ style works. `stat-list` also exports its `StatItem` type, and
521
+ `data-table-toolbar` its `ChipGroup` / `ChipSpec` types.
522
+
523
+ Each component is its own subpath export (`@poodle64/ui/<name>`), matching
524
+ shadcn-svelte's own convention; a flat barrel would collide on the generic names
525
+ (`Root`, `Content`, `Trigger`) that most primitives share.
526
+
527
+ **`CardTitle` takes an optional `level`.** It renders a `<div>` by default, as
528
+ upstream shadcn does, because a card title is not always a heading. Where it IS
529
+ the heading for that card's content — the common case on a dashboard — pass the
530
+ level and it becomes a real `<h1>`–`<h6>`:
531
+
532
+ ```svelte
533
+ <Card.Title level={2}>Estate summary</Card.Title>
534
+ ```
535
+
536
+ Nothing else changes: the class list is identical either way, so `level` moves
537
+ the document outline and not a pixel. Worth knowing before a migration, because
538
+ the default is silent — an app replacing its own `<h3>` card titles with this
539
+ component loses every heading below its page `<h1>` with no error, no lint hit
540
+ and no visual difference.
541
+
542
+ **Two lines in the app's `app.css`**, after the token imports:
543
+
544
+ ```css
545
+ @import '@poodle64/design-tokens/tokens.tw.css';
546
+ @import '@poodle64/design-tokens/tokens.css';
547
+ @import '@poodle64/ui/styles.css'; /* required: theme registration + component styles */
548
+ @source '../node_modules/@poodle64/ui/dist'; /* Tailwind content scan */
549
+ ```
550
+
551
+ `@poodle64/ui/styles.css` is **required, not optional**. It carries two things.
552
+
553
+ First, the shadcn semantic surface: `--card`, `--popover`, `--muted`, `--accent`,
554
+ `--secondary`, `--input` and their `-foreground` pairs, each mapped to a `--ds-*`
555
+ token and registered with Tailwind so `bg-card`, `bg-muted`, `border-input` and
556
+ friends actually generate CSS. Declaring those names as plain custom properties
557
+ is not enough: the variable exists, Tailwind still does not know it is a colour,
558
+ and the utility compiles to no rule at all. That is a silent failure with no
559
+ build error, no lint hit and no failing test, which is exactly how it went
560
+ unnoticed through a full app migration (#3).
561
+
562
+ Second, what the variable layer does not cover: the `text-display` / `text-body` /
563
+ `text-stat` / `tracking-eyebrow` scale keys, the `.ds-edge` card treatment, the
564
+ `.ds-dialog-section` divider rule, the `.ds-chip` / `.ds-dot` / `.ds-ink` status
565
+ classes, and the two things that make this package's overlays animate at all —
566
+ `@custom-variant data-open` / `data-closed` (bits-ui emits `data-state="open"`,
567
+ so a bare `data-open:` utility matches nothing without them) and an import of
568
+ `tw-animate-css`, which defines `animate-in`, `fade-in-0`, `zoom-in-95` and
569
+ `slide-in-from-*`. Both used to be the app's job, and an app that never did the
570
+ job got dialogues, dropdowns, popovers and selects that opened with no
571
+ transition, silently. A consuming app now declares neither; one that already
572
+ declares them loses nothing, since the definitions are identical.
573
+
574
+ It holds no palette. Every value resolves through a `--ds-*` token, so choosing a
575
+ palette stays a matter of overriding `--ds-color-*` in your own `app.css`, and
576
+ `.dark` flows through untouched. Any single name can still be overridden by
577
+ re-declaring it after the import, which is now a deliberate deviation rather than
578
+ setup every app has to repeat.
579
+
580
+ Sidebar and chart colours are not registered here: this package ships no sidebar
581
+ or chart component, and those are the one part of the surface apps genuinely
582
+ differ on. An app using them registers them itself.
583
+
584
+ `@source` is needed because the package's Tailwind classes live in
585
+ `node_modules/@poodle64/ui/dist`, outside the app's own `src/`, so the default
586
+ source scan misses them. Without it the components render unstyled (no build
587
+ error, no lint hit; the classes just never reach the compiled CSS).
588
+
589
+ ## Verifying a change
590
+
591
+ ```bash
592
+ pnpm test # build + vitest: jsdom interaction, and compiled-CSS gates
593
+ pnpm run check # svelte-check
594
+ pnpm run test:browser # build the harness and drive it in a real browser
595
+
596
+ # Before releasing a change that claims to be additive (minutes, not seconds):
597
+ node harness/additivity.mjs ui-v2026.8.3
598
+ ```
599
+
600
+ `pnpm test` includes the gates that compile the built package through the real
601
+ Tailwind chain, exactly as a consuming app wires it — a class that generates no
602
+ rule, a colour that resolves to nothing, a registered theme key nothing reads, a
603
+ variant whose selector cannot match the DOM, and a sizing utility that has
604
+ silently changed meaning all fail there rather than shipping.
605
+
606
+ `pnpm run test:browser` is the leg nothing else can stand in for: jsdom applies
607
+ no stylesheet and returns unresolved `var(…)` literals, so it passes just as
608
+ happily on a dead animation, an unregistered colour and a layout that does not
609
+ exist. `harness/drive.md` records what is asserted, what each defect looked like
610
+ before it was caught, and the measurement traps worth inheriting.
611
+
612
+ `harness/additivity.mjs` is the leg that runs across two builds. Every additive
613
+ prop this package has added promised the same thing — that a consumer who never
614
+ names it renders identically — and that promise used to be checked by hand and
615
+ written up as prose nobody could re-derive. It now builds the base ref in a
616
+ throwaway worktree and diffs the markup, the computed boxes, the geometry and the
617
+ pixels of the surfaces a consumer gets without opting into anything, exiting
618
+ non-zero and naming the field on any difference.
619
+
620
+ ## Releasing
621
+
622
+ 1. Change a component; bump `version` in `package.json` (CalVer).
623
+ 2. `pnpm build`, which runs `svelte-package` then `publint` (package.json/exports sanity).
624
+ 3. Commit, tag `ui-v<version>`, push the tag; CI publishes to public npm.
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cn, type WithElementRef } from '../../../utils';
4
+ import type { Snippet } from 'svelte';
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ children,
10
+ ...restProps
11
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
12
+ children?: Snippet;
13
+ } = $props();
14
+ </script>
15
+
16
+ <div
17
+ bind:this={ref}
18
+ data-slot="alert-description"
19
+ class={cn('text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm', className)}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import { type WithElementRef } from '../../../utils';
3
+ import type { Snippet } from 'svelte';
4
+ type $$ComponentProps = WithElementRef<HTMLAttributes<HTMLDivElement>> & {
5
+ children?: Snippet;
6
+ };
7
+ declare const AlertDescription: import("svelte").Component<$$ComponentProps, {}, "ref">;
8
+ type AlertDescription = ReturnType<typeof AlertDescription>;
9
+ export default AlertDescription;