@gabvdl/ui 0.0.14

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 (330) hide show
  1. package/README.md +95 -0
  2. package/dist/components/button/button.d.ts +39 -0
  3. package/dist/components/button/button.d.ts.map +1 -0
  4. package/dist/components/button/button.js +72 -0
  5. package/dist/components/button/button.js.map +1 -0
  6. package/dist/components/button/index.d.ts +5 -0
  7. package/dist/components/button/index.d.ts.map +1 -0
  8. package/dist/components/button/tooltip.d.ts +31 -0
  9. package/dist/components/button/tooltip.d.ts.map +1 -0
  10. package/dist/components/button/tooltip.js +83 -0
  11. package/dist/components/button/tooltip.js.map +1 -0
  12. package/dist/components/changelog/changelog.d.ts +52 -0
  13. package/dist/components/changelog/changelog.d.ts.map +1 -0
  14. package/dist/components/changelog/changelog.js +164 -0
  15. package/dist/components/changelog/changelog.js.map +1 -0
  16. package/dist/components/changelog/index.d.ts +3 -0
  17. package/dist/components/changelog/index.d.ts.map +1 -0
  18. package/dist/components/copy-button/copy-button.d.ts +16 -0
  19. package/dist/components/copy-button/copy-button.d.ts.map +1 -0
  20. package/dist/components/copy-button/copy-button.js +40 -0
  21. package/dist/components/copy-button/copy-button.js.map +1 -0
  22. package/dist/components/copy-button/index.d.ts +3 -0
  23. package/dist/components/copy-button/index.d.ts.map +1 -0
  24. package/dist/components/drop-zone/drop-zone.d.ts +20 -0
  25. package/dist/components/drop-zone/drop-zone.d.ts.map +1 -0
  26. package/dist/components/drop-zone/drop-zone.js +54 -0
  27. package/dist/components/drop-zone/drop-zone.js.map +1 -0
  28. package/dist/components/drop-zone/index.d.ts +5 -0
  29. package/dist/components/drop-zone/index.d.ts.map +1 -0
  30. package/dist/components/drop-zone/use-file-drop.d.ts +45 -0
  31. package/dist/components/drop-zone/use-file-drop.d.ts.map +1 -0
  32. package/dist/components/drop-zone/use-file-drop.js +97 -0
  33. package/dist/components/drop-zone/use-file-drop.js.map +1 -0
  34. package/dist/components/element-picker/element-picker-field.d.ts +30 -0
  35. package/dist/components/element-picker/element-picker-field.d.ts.map +1 -0
  36. package/dist/components/element-picker/element-picker-field.js +273 -0
  37. package/dist/components/element-picker/element-picker-field.js.map +1 -0
  38. package/dist/components/element-picker/element-picker.d.ts +45 -0
  39. package/dist/components/element-picker/element-picker.d.ts.map +1 -0
  40. package/dist/components/element-picker/element-picker.js +179 -0
  41. package/dist/components/element-picker/element-picker.js.map +1 -0
  42. package/dist/components/element-picker/element-preview.d.ts +23 -0
  43. package/dist/components/element-picker/element-preview.d.ts.map +1 -0
  44. package/dist/components/element-picker/element-preview.js +72 -0
  45. package/dist/components/element-picker/element-preview.js.map +1 -0
  46. package/dist/components/element-picker/index.d.ts +12 -0
  47. package/dist/components/element-picker/index.d.ts.map +1 -0
  48. package/dist/components/element-picker/parse.d.ts +32 -0
  49. package/dist/components/element-picker/parse.d.ts.map +1 -0
  50. package/dist/components/element-picker/parse.js +230 -0
  51. package/dist/components/element-picker/parse.js.map +1 -0
  52. package/dist/components/element-picker/types.d.ts +72 -0
  53. package/dist/components/element-picker/types.d.ts.map +1 -0
  54. package/dist/components/element-picker/use-element-picker.d.ts +56 -0
  55. package/dist/components/element-picker/use-element-picker.d.ts.map +1 -0
  56. package/dist/components/element-picker/use-element-picker.js +201 -0
  57. package/dist/components/element-picker/use-element-picker.js.map +1 -0
  58. package/dist/components/empty-state/empty-state.d.ts +16 -0
  59. package/dist/components/empty-state/empty-state.d.ts.map +1 -0
  60. package/dist/components/empty-state/empty-state.js +41 -0
  61. package/dist/components/empty-state/empty-state.js.map +1 -0
  62. package/dist/components/empty-state/index.d.ts +3 -0
  63. package/dist/components/empty-state/index.d.ts.map +1 -0
  64. package/dist/components/floating-panel/dock-context.d.ts +66 -0
  65. package/dist/components/floating-panel/dock-context.d.ts.map +1 -0
  66. package/dist/components/floating-panel/dock-context.js +96 -0
  67. package/dist/components/floating-panel/dock-context.js.map +1 -0
  68. package/dist/components/floating-panel/dock.d.ts +26 -0
  69. package/dist/components/floating-panel/dock.d.ts.map +1 -0
  70. package/dist/components/floating-panel/dock.js +46 -0
  71. package/dist/components/floating-panel/dock.js.map +1 -0
  72. package/dist/components/floating-panel/floating-panel.d.ts +69 -0
  73. package/dist/components/floating-panel/floating-panel.d.ts.map +1 -0
  74. package/dist/components/floating-panel/floating-panel.js +285 -0
  75. package/dist/components/floating-panel/floating-panel.js.map +1 -0
  76. package/dist/components/floating-panel/index.d.ts +7 -0
  77. package/dist/components/floating-panel/index.d.ts.map +1 -0
  78. package/dist/components/fuzzy-list/fuzzy-list.d.ts +96 -0
  79. package/dist/components/fuzzy-list/fuzzy-list.d.ts.map +1 -0
  80. package/dist/components/fuzzy-list/fuzzy-list.js +127 -0
  81. package/dist/components/fuzzy-list/fuzzy-list.js.map +1 -0
  82. package/dist/components/fuzzy-list/index.d.ts +4 -0
  83. package/dist/components/fuzzy-list/index.d.ts.map +1 -0
  84. package/dist/components/global-search/global-search.d.ts +78 -0
  85. package/dist/components/global-search/global-search.d.ts.map +1 -0
  86. package/dist/components/global-search/global-search.js +181 -0
  87. package/dist/components/global-search/global-search.js.map +1 -0
  88. package/dist/components/global-search/hotkey.d.ts +23 -0
  89. package/dist/components/global-search/hotkey.d.ts.map +1 -0
  90. package/dist/components/global-search/hotkey.js +41 -0
  91. package/dist/components/global-search/hotkey.js.map +1 -0
  92. package/dist/components/global-search/index.d.ts +5 -0
  93. package/dist/components/global-search/index.d.ts.map +1 -0
  94. package/dist/components/iframe-preview/iframe-preview.d.ts +64 -0
  95. package/dist/components/iframe-preview/iframe-preview.d.ts.map +1 -0
  96. package/dist/components/iframe-preview/iframe-preview.js +257 -0
  97. package/dist/components/iframe-preview/iframe-preview.js.map +1 -0
  98. package/dist/components/iframe-preview/index.d.ts +3 -0
  99. package/dist/components/iframe-preview/index.d.ts.map +1 -0
  100. package/dist/components/image-viewer/image-viewer.d.ts +30 -0
  101. package/dist/components/image-viewer/image-viewer.d.ts.map +1 -0
  102. package/dist/components/image-viewer/image-viewer.js +226 -0
  103. package/dist/components/image-viewer/image-viewer.js.map +1 -0
  104. package/dist/components/image-viewer/index.d.ts +4 -0
  105. package/dist/components/image-viewer/index.d.ts.map +1 -0
  106. package/dist/components/image-viewer/progressive-image.d.ts +20 -0
  107. package/dist/components/image-viewer/progressive-image.d.ts.map +1 -0
  108. package/dist/components/image-viewer/progressive-image.js +62 -0
  109. package/dist/components/image-viewer/progressive-image.js.map +1 -0
  110. package/dist/components/image-viewer/viewable-image.d.ts +17 -0
  111. package/dist/components/image-viewer/viewable-image.d.ts.map +1 -0
  112. package/dist/components/image-viewer/viewable-image.js +38 -0
  113. package/dist/components/image-viewer/viewable-image.js.map +1 -0
  114. package/dist/components/input/index.d.ts +3 -0
  115. package/dist/components/input/index.d.ts.map +1 -0
  116. package/dist/components/input/input.d.ts +15 -0
  117. package/dist/components/input/input.d.ts.map +1 -0
  118. package/dist/components/input/input.js +56 -0
  119. package/dist/components/input/input.js.map +1 -0
  120. package/dist/components/modal/index.d.ts +5 -0
  121. package/dist/components/modal/index.d.ts.map +1 -0
  122. package/dist/components/modal/modal-context.d.ts +37 -0
  123. package/dist/components/modal/modal-context.d.ts.map +1 -0
  124. package/dist/components/modal/modal-context.js +81 -0
  125. package/dist/components/modal/modal-context.js.map +1 -0
  126. package/dist/components/modal/modal.d.ts +28 -0
  127. package/dist/components/modal/modal.d.ts.map +1 -0
  128. package/dist/components/modal/modal.js +94 -0
  129. package/dist/components/modal/modal.js.map +1 -0
  130. package/dist/components/nav-2d/index.d.ts +3 -0
  131. package/dist/components/nav-2d/index.d.ts.map +1 -0
  132. package/dist/components/nav-2d/nav-2d.d.ts +59 -0
  133. package/dist/components/nav-2d/nav-2d.d.ts.map +1 -0
  134. package/dist/components/nav-2d/nav-2d.js +290 -0
  135. package/dist/components/nav-2d/nav-2d.js.map +1 -0
  136. package/dist/components/phone-preview/index.d.ts +3 -0
  137. package/dist/components/phone-preview/index.d.ts.map +1 -0
  138. package/dist/components/phone-preview/phone-preview.d.ts +45 -0
  139. package/dist/components/phone-preview/phone-preview.d.ts.map +1 -0
  140. package/dist/components/phone-preview/phone-preview.js +117 -0
  141. package/dist/components/phone-preview/phone-preview.js.map +1 -0
  142. package/dist/components/progressive-bash/index.d.ts +7 -0
  143. package/dist/components/progressive-bash/index.d.ts.map +1 -0
  144. package/dist/components/progressive-bash/parse.d.ts +88 -0
  145. package/dist/components/progressive-bash/parse.d.ts.map +1 -0
  146. package/dist/components/progressive-bash/parse.js +120 -0
  147. package/dist/components/progressive-bash/parse.js.map +1 -0
  148. package/dist/components/progressive-bash/playback.d.ts +50 -0
  149. package/dist/components/progressive-bash/playback.d.ts.map +1 -0
  150. package/dist/components/progressive-bash/playback.js +19 -0
  151. package/dist/components/progressive-bash/playback.js.map +1 -0
  152. package/dist/components/progressive-bash/progressive-bash.d.ts +97 -0
  153. package/dist/components/progressive-bash/progressive-bash.d.ts.map +1 -0
  154. package/dist/components/progressive-bash/progressive-bash.js +377 -0
  155. package/dist/components/progressive-bash/progressive-bash.js.map +1 -0
  156. package/dist/components/progressive-list/index.d.ts +3 -0
  157. package/dist/components/progressive-list/index.d.ts.map +1 -0
  158. package/dist/components/progressive-list/progressive-list.d.ts +55 -0
  159. package/dist/components/progressive-list/progressive-list.d.ts.map +1 -0
  160. package/dist/components/progressive-list/progressive-list.js +83 -0
  161. package/dist/components/progressive-list/progressive-list.js.map +1 -0
  162. package/dist/components/progressive-table/index.d.ts +3 -0
  163. package/dist/components/progressive-table/index.d.ts.map +1 -0
  164. package/dist/components/progressive-table/progressive-table.d.ts +58 -0
  165. package/dist/components/progressive-table/progressive-table.d.ts.map +1 -0
  166. package/dist/components/progressive-table/progressive-table.js +86 -0
  167. package/dist/components/progressive-table/progressive-table.js.map +1 -0
  168. package/dist/components/progressive-text/index.d.ts +3 -0
  169. package/dist/components/progressive-text/index.d.ts.map +1 -0
  170. package/dist/components/progressive-text/progressive-text.d.ts +54 -0
  171. package/dist/components/progressive-text/progressive-text.d.ts.map +1 -0
  172. package/dist/components/progressive-text/progressive-text.js +97 -0
  173. package/dist/components/progressive-text/progressive-text.js.map +1 -0
  174. package/dist/components/progressive-timeline/index.d.ts +3 -0
  175. package/dist/components/progressive-timeline/index.d.ts.map +1 -0
  176. package/dist/components/progressive-timeline/progressive-timeline.d.ts +46 -0
  177. package/dist/components/progressive-timeline/progressive-timeline.d.ts.map +1 -0
  178. package/dist/components/progressive-timeline/progressive-timeline.js +62 -0
  179. package/dist/components/progressive-timeline/progressive-timeline.js.map +1 -0
  180. package/dist/components/relative-time/index.d.ts +3 -0
  181. package/dist/components/relative-time/index.d.ts.map +1 -0
  182. package/dist/components/relative-time/relative-time.d.ts +14 -0
  183. package/dist/components/relative-time/relative-time.d.ts.map +1 -0
  184. package/dist/components/relative-time/relative-time.js +15 -0
  185. package/dist/components/relative-time/relative-time.js.map +1 -0
  186. package/dist/components/resizable-layout/index.d.ts +3 -0
  187. package/dist/components/resizable-layout/index.d.ts.map +1 -0
  188. package/dist/components/resizable-layout/resizable-layout.d.ts +113 -0
  189. package/dist/components/resizable-layout/resizable-layout.d.ts.map +1 -0
  190. package/dist/components/resizable-layout/resizable-layout.js +398 -0
  191. package/dist/components/resizable-layout/resizable-layout.js.map +1 -0
  192. package/dist/components/rich-input/compose.d.ts +13 -0
  193. package/dist/components/rich-input/compose.d.ts.map +1 -0
  194. package/dist/components/rich-input/compose.js +28 -0
  195. package/dist/components/rich-input/compose.js.map +1 -0
  196. package/dist/components/rich-input/index.d.ts +5 -0
  197. package/dist/components/rich-input/index.d.ts.map +1 -0
  198. package/dist/components/rich-input/parts.d.ts +53 -0
  199. package/dist/components/rich-input/parts.d.ts.map +1 -0
  200. package/dist/components/rich-input/parts.js +285 -0
  201. package/dist/components/rich-input/parts.js.map +1 -0
  202. package/dist/components/rich-input/rich-input.d.ts +64 -0
  203. package/dist/components/rich-input/rich-input.d.ts.map +1 -0
  204. package/dist/components/rich-input/rich-input.js +351 -0
  205. package/dist/components/rich-input/rich-input.js.map +1 -0
  206. package/dist/components/rich-input/types.d.ts +92 -0
  207. package/dist/components/rich-input/types.d.ts.map +1 -0
  208. package/dist/components/rich-input/use-draft.d.ts +9 -0
  209. package/dist/components/rich-input/use-draft.d.ts.map +1 -0
  210. package/dist/components/rich-input/use-draft.js +47 -0
  211. package/dist/components/rich-input/use-draft.js.map +1 -0
  212. package/dist/components/rich-input/use-file-upload.d.ts +18 -0
  213. package/dist/components/rich-input/use-file-upload.d.ts.map +1 -0
  214. package/dist/components/rich-input/use-file-upload.js +88 -0
  215. package/dist/components/rich-input/use-file-upload.js.map +1 -0
  216. package/dist/components/rich-input/use-guidelines.d.ts +16 -0
  217. package/dist/components/rich-input/use-guidelines.d.ts.map +1 -0
  218. package/dist/components/rich-input/use-guidelines.js +85 -0
  219. package/dist/components/rich-input/use-guidelines.js.map +1 -0
  220. package/dist/components/rich-input/use-input-history.d.ts +22 -0
  221. package/dist/components/rich-input/use-input-history.d.ts.map +1 -0
  222. package/dist/components/rich-input/use-input-history.js +61 -0
  223. package/dist/components/rich-input/use-input-history.js.map +1 -0
  224. package/dist/components/rich-input/use-mention.d.ts +24 -0
  225. package/dist/components/rich-input/use-mention.d.ts.map +1 -0
  226. package/dist/components/rich-input/use-mention.js +54 -0
  227. package/dist/components/rich-input/use-mention.js.map +1 -0
  228. package/dist/components/search-input/index.d.ts +3 -0
  229. package/dist/components/search-input/index.d.ts.map +1 -0
  230. package/dist/components/search-input/search-input.d.ts +14 -0
  231. package/dist/components/search-input/search-input.d.ts.map +1 -0
  232. package/dist/components/search-input/search-input.js +63 -0
  233. package/dist/components/search-input/search-input.js.map +1 -0
  234. package/dist/components/skeleton/index.d.ts +3 -0
  235. package/dist/components/skeleton/index.d.ts.map +1 -0
  236. package/dist/components/skeleton/skeleton.d.ts +19 -0
  237. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  238. package/dist/components/skeleton/skeleton.js +29 -0
  239. package/dist/components/skeleton/skeleton.js.map +1 -0
  240. package/dist/components/spinner/index.d.ts +3 -0
  241. package/dist/components/spinner/index.d.ts.map +1 -0
  242. package/dist/components/spinner/spinner.d.ts +11 -0
  243. package/dist/components/spinner/spinner.d.ts.map +1 -0
  244. package/dist/components/spinner/spinner.js +32 -0
  245. package/dist/components/spinner/spinner.js.map +1 -0
  246. package/dist/components/status-badge/index.d.ts +3 -0
  247. package/dist/components/status-badge/index.d.ts.map +1 -0
  248. package/dist/components/status-badge/status-badge.d.ts +31 -0
  249. package/dist/components/status-badge/status-badge.d.ts.map +1 -0
  250. package/dist/components/status-badge/status-badge.js +53 -0
  251. package/dist/components/status-badge/status-badge.js.map +1 -0
  252. package/dist/components/theme/index.d.ts +5 -0
  253. package/dist/components/theme/index.d.ts.map +1 -0
  254. package/dist/components/theme/theme-toggle.d.ts +11 -0
  255. package/dist/components/theme/theme-toggle.d.ts.map +1 -0
  256. package/dist/components/theme/theme-toggle.js +57 -0
  257. package/dist/components/theme/theme-toggle.js.map +1 -0
  258. package/dist/components/theme/theme.d.ts +29 -0
  259. package/dist/components/theme/theme.d.ts.map +1 -0
  260. package/dist/components/theme/theme.js +70 -0
  261. package/dist/components/theme/theme.js.map +1 -0
  262. package/dist/components/toast/index.d.ts +3 -0
  263. package/dist/components/toast/index.d.ts.map +1 -0
  264. package/dist/components/toast/toast.d.ts +64 -0
  265. package/dist/components/toast/toast.d.ts.map +1 -0
  266. package/dist/components/toast/toast.js +155 -0
  267. package/dist/components/toast/toast.js.map +1 -0
  268. package/dist/components/virtual-list/index.d.ts +3 -0
  269. package/dist/components/virtual-list/index.d.ts.map +1 -0
  270. package/dist/components/virtual-list/virtual-list.d.ts +71 -0
  271. package/dist/components/virtual-list/virtual-list.d.ts.map +1 -0
  272. package/dist/components/virtual-list/virtual-list.js +65 -0
  273. package/dist/components/virtual-list/virtual-list.js.map +1 -0
  274. package/dist/hooks/index.d.ts +12 -0
  275. package/dist/hooks/index.d.ts.map +1 -0
  276. package/dist/hooks/use-copy-to-clipboard.d.ts +26 -0
  277. package/dist/hooks/use-copy-to-clipboard.d.ts.map +1 -0
  278. package/dist/hooks/use-copy-to-clipboard.js +36 -0
  279. package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
  280. package/dist/hooks/use-debounced-value.d.ts +23 -0
  281. package/dist/hooks/use-debounced-value.d.ts.map +1 -0
  282. package/dist/hooks/use-debounced-value.js +19 -0
  283. package/dist/hooks/use-debounced-value.js.map +1 -0
  284. package/dist/hooks/use-intersection.d.ts +25 -0
  285. package/dist/hooks/use-intersection.d.ts.map +1 -0
  286. package/dist/hooks/use-intersection.js +38 -0
  287. package/dist/hooks/use-intersection.js.map +1 -0
  288. package/dist/hooks/use-local-storage.d.ts +12 -0
  289. package/dist/hooks/use-local-storage.d.ts.map +1 -0
  290. package/dist/hooks/use-local-storage.js +43 -0
  291. package/dist/hooks/use-local-storage.js.map +1 -0
  292. package/dist/hooks/use-long-press.d.ts +37 -0
  293. package/dist/hooks/use-long-press.d.ts.map +1 -0
  294. package/dist/hooks/use-long-press.js +39 -0
  295. package/dist/hooks/use-long-press.js.map +1 -0
  296. package/dist/hooks/use-media-query.d.ts +17 -0
  297. package/dist/hooks/use-media-query.d.ts.map +1 -0
  298. package/dist/hooks/use-media-query.js +37 -0
  299. package/dist/hooks/use-media-query.js.map +1 -0
  300. package/dist/hooks/use-overlay.d.ts +20 -0
  301. package/dist/hooks/use-overlay.d.ts.map +1 -0
  302. package/dist/hooks/use-overlay.js +42 -0
  303. package/dist/hooks/use-overlay.js.map +1 -0
  304. package/dist/image-viewer.css +152 -0
  305. package/dist/index.d.ts +67 -0
  306. package/dist/index.d.ts.map +1 -0
  307. package/dist/index.js +161 -0
  308. package/dist/index.js.map +1 -0
  309. package/dist/lib/format.d.ts +19 -0
  310. package/dist/lib/format.d.ts.map +1 -0
  311. package/dist/lib/format.js +62 -0
  312. package/dist/lib/format.js.map +1 -0
  313. package/dist/lib/highlight.d.ts +15 -0
  314. package/dist/lib/highlight.d.ts.map +1 -0
  315. package/dist/lib/highlight.js +39 -0
  316. package/dist/lib/highlight.js.map +1 -0
  317. package/dist/lib/search.d.ts +40 -0
  318. package/dist/lib/search.d.ts.map +1 -0
  319. package/dist/lib/search.js +91 -0
  320. package/dist/lib/search.js.map +1 -0
  321. package/dist/lib/utils.d.ts +4 -0
  322. package/dist/lib/utils.d.ts.map +1 -0
  323. package/dist/lib/utils.js +9 -0
  324. package/dist/lib/utils.js.map +1 -0
  325. package/dist/overlay.css +78 -0
  326. package/dist/styles.css +6 -0
  327. package/dist/theme.css +81 -0
  328. package/dist/tooltip.css +53 -0
  329. package/dist/virtual-list.css +29 -0
  330. package/package.json +77 -0
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @gabvdl/ui
2
+
3
+ > ⚠️ **Very early alpha.** Expect breaking changes — the API moves fast and any
4
+ > `0.0.x` bump may break you. Pin an exact version. `1.0.0`, coming shortly,
5
+ > will be the first stable cut.
6
+
7
+ Gabriel Vidal's personal design system — tree-shakeable React components built on
8
+ shadcn primitives, reused across the homelab. Flagship: a full-screen image
9
+ viewer with zoom, pan and swipe.
10
+
11
+ Source is mirrored at
12
+ **[github.com/GabrielVidal1/design-system](https://github.com/GabrielVidal1/design-system)**;
13
+ the docs + live demo are at **[ui.gabvdl.xyz](https://ui.gabvdl.xyz)**.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @gabvdl/ui
19
+ ```
20
+
21
+ ## Use
22
+
23
+ ```tsx
24
+ import { ImageViewerProvider, ViewableImage } from '@gabvdl/ui'
25
+ import '@gabvdl/ui/styles.css'
26
+
27
+ export function Gallery({ urls }: { urls: string[] }) {
28
+ return (
29
+ <ImageViewerProvider>
30
+ {urls.map((u) => (
31
+ <ViewableImage key={u} src={u} />
32
+ ))}
33
+ </ImageViewerProvider>
34
+ )
35
+ }
36
+ ```
37
+
38
+ Peer deps: `react >=18`, `react-dom >=18`. Ships ESM + type declarations, and
39
+ side-effect-free except the CSS entry points (`styles.css`, `theme.css`,
40
+ `image-viewer.css`, `virtual-list.css`).
41
+
42
+ ## Search: `FuzzyList` and `GlobalSearch`
43
+
44
+ `FuzzyList` is a Fuse.js search box over a windowed `VirtualList`. Two things
45
+ shape how it searches:
46
+
47
+ - **Quoting pins an exact match.** A bare query fuzzy-matches; any
48
+ `"double-quoted"` segment must appear as a literal, case-insensitive substring.
49
+ They mix — `parser "utils.ts"` fuzzy-matches `parser` and then keeps only rows
50
+ whose fields contain `utils.ts`. Both kinds of hit are `<mark>`-highlighted.
51
+ - **The search is debounced** by `debounce` ms (default `400`). The input stays
52
+ instant; only the Fuse pass and the list re-render trail behind it, and a small
53
+ spinner shows while they do. `debounce={0}` searches on every keystroke.
54
+
55
+ `GlobalSearch` is the Cmd-K palette built from those pieces — a `Modal` holding a
56
+ `FuzzyList`. `items` may be an array *or a loader*, called the first time the
57
+ palette opens, which is how you lazily pull a build-time search index:
58
+
59
+ ```tsx
60
+ <GlobalSearch
61
+ searchKey="Ctrl+K" // "Mod+K" = ⌘ on Apple, Ctrl elsewhere · null = off
62
+ items={() => fetch('/search-index.json').then((r) => r.json())}
63
+ keys={[{ name: 'name', weight: 3 }, 'summary', 'props']}
64
+ titleKey="name"
65
+ descriptionKey="summary"
66
+ badgeKey="kind"
67
+ trigger="bar" // or "icon" (default) / "none" + controlled `open`
68
+ onSelect={(entry) => navigate(entry.route)}
69
+ />
70
+ ```
71
+
72
+ ### Generating that index at build time
73
+
74
+ The docs app ([`apps/docs/plugins/search-index.ts`](../../apps/docs/plugins/search-index.ts))
75
+ is the reference implementation: a Vite plugin that walks `packages/ui/src` with
76
+ the **TypeScript compiler API**, and for every symbol re-exported from
77
+ `src/index.ts` emits an entry — components, hooks, utilities, **and one entry per
78
+ prop** (`FuzzyList.debounce`, with its type and TSDoc). It writes
79
+ `public/search-index.json` on `buildStart` and regenerates on source changes in
80
+ dev, so nothing is hand-maintained: add a component and it is searchable on the
81
+ next build.
82
+
83
+ ## Publishing (maintainer)
84
+
85
+ Public npm releases ride on a version tag — see [`RELEASING.md`](../../RELEASING.md)
86
+ at the repo root. Dev builds still go to the homelab's private verdaccio
87
+ (browse it at [registry.lab.gabvdl.xyz](https://registry.lab.gabvdl.xyz)):
88
+
89
+ ```bash
90
+ # from the homelab repo root, with the registry profile up:
91
+ ./services/registry/publish.sh
92
+ ```
93
+
94
+ Bump `version` in this package's `package.json` first — verdaccio rejects
95
+ re-publishing an existing version.
@@ -0,0 +1,39 @@
1
+ import { TooltipSide } from './tooltip';
2
+ import * as React from 'react';
3
+ export type ButtonVariant = 'default' | 'outline' | 'ghost' | 'destructive';
4
+ /**
5
+ * Three text tiers (`sm` / `md` / `lg`) plus a square icon-only tier for each.
6
+ * `default` and `icon` are kept as aliases of `md` / `icon-md`.
7
+ */
8
+ export type ButtonSize = 'sm' | 'md' | 'lg' | 'icon-sm' | 'icon-md' | 'icon-lg' | 'default' | 'icon';
9
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
10
+ variant?: ButtonVariant;
11
+ size?: ButtonSize;
12
+ /**
13
+ * Swap the leading icon for a spinner, disable the button and mark it
14
+ * `aria-busy`. The label stays put so the button does not resize.
15
+ */
16
+ loading?: boolean;
17
+ /** Announced (and shown, on a text-tier button) in place of the label while `loading`. */
18
+ loadingText?: string;
19
+ /** Leading icon — or the only content, on an `icon-*` tier. */
20
+ icon?: React.ReactNode;
21
+ /** Put `icon` after the label instead of before it. */
22
+ iconPosition?: 'left' | 'right';
23
+ /**
24
+ * Hover/focus hint. On an icon-only button it also becomes the accessible
25
+ * name when no `aria-label` is given; otherwise it is wired up as
26
+ * `aria-describedby`.
27
+ */
28
+ tooltip?: React.ReactNode;
29
+ tooltipSide?: TooltipSide;
30
+ }
31
+ /**
32
+ * The design-system button: four variants, three size tiers (icon-only twins of
33
+ * each), an icon slot, a loading state and an optional tooltip.
34
+ *
35
+ * `type` defaults to `"button"` so dropping one inside a form never submits it
36
+ * by accident — pass `type="submit"` when that is what you want.
37
+ */
38
+ export declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
39
+ //# sourceMappingURL=button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAW,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAEtD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,CAAC;AAE5E;;;GAGG;AACH,MAAM,MAAM,UAAU,GAClB,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,MAAM,CAAC;AA2BX,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IAChF,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,uFAoEjB,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { jsxs as _, jsx as n } from "react/jsx-runtime";
2
+ import { Loader2 as w } from "lucide-react";
3
+ import * as N from "react";
4
+ import { cn as L } from "../../lib/utils.js";
5
+ import { Tooltip as j } from "./tooltip.js";
6
+ const O = {
7
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
8
+ outline: "border border-border bg-transparent hover:bg-muted",
9
+ ghost: "hover:bg-muted",
10
+ destructive: "text-destructive hover:bg-destructive/10"
11
+ }, k = {
12
+ sm: "h-8 px-3 gap-1.5 text-xs [&_svg]:size-3.5",
13
+ md: "h-9 px-4 gap-2 text-sm [&_svg]:size-4",
14
+ lg: "h-11 px-6 gap-2.5 text-base [&_svg]:size-5",
15
+ "icon-sm": "size-8 [&_svg]:size-3.5",
16
+ "icon-md": "size-9 [&_svg]:size-4",
17
+ "icon-lg": "size-11 [&_svg]:size-5",
18
+ default: "h-9 px-4 gap-2 text-sm [&_svg]:size-4",
19
+ icon: "size-9 [&_svg]:size-4"
20
+ }, B = /* @__PURE__ */ new Set([
21
+ "icon-sm",
22
+ "icon-md",
23
+ "icon-lg",
24
+ "icon"
25
+ ]), q = N.forwardRef(function({
26
+ className: u,
27
+ variant: g = "default",
28
+ size: o = "default",
29
+ type: b = "button",
30
+ loading: e = !1,
31
+ loadingText: i,
32
+ icon: f,
33
+ iconPosition: a = "left",
34
+ tooltip: s,
35
+ tooltipSide: v = "top",
36
+ disabled: p,
37
+ children: h,
38
+ ...r
39
+ }, x) {
40
+ const c = B.has(o), d = e && !!i && !c, z = d ? i : h, t = e ? /* @__PURE__ */ n(w, { className: "animate-spin", "aria-hidden": "true" }) : f, l = c && typeof s == "string" && !r["aria-label"], y = r["aria-label"] ?? (l ? s : void 0), m = /* @__PURE__ */ _(
41
+ "button",
42
+ {
43
+ ...r,
44
+ ref: x,
45
+ type: b,
46
+ "aria-label": y,
47
+ "aria-busy": e || void 0,
48
+ disabled: p || e,
49
+ className: L(
50
+ "inline-flex shrink-0 items-center justify-center rounded-lg font-medium whitespace-nowrap transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 [&_svg]:shrink-0",
51
+ // A disabled button normally stops taking pointer events at all — but a
52
+ // tooltip is exactly what you want to read when a button is disabled or
53
+ // busy, so keep it hoverable in that case.
54
+ s ? "disabled:cursor-not-allowed" : "disabled:pointer-events-none",
55
+ O[g],
56
+ k[o],
57
+ u
58
+ ),
59
+ children: [
60
+ t && a === "left" && t,
61
+ z,
62
+ t && a === "right" && t,
63
+ e && !d && /* @__PURE__ */ n("span", { className: "sr-only", role: "status", children: i ?? "Loading" })
64
+ ]
65
+ }
66
+ );
67
+ return s ? /* @__PURE__ */ n(j, { content: s, side: v, describes: !l, children: m }) : m;
68
+ });
69
+ export {
70
+ q as Button
71
+ };
72
+ //# sourceMappingURL=button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.js","sources":["../../../src/components/button/button.tsx"],"sourcesContent":["import { Loader2 } from 'lucide-react';\nimport * as React from 'react';\n\nimport { cn } from '../../lib/utils';\nimport { Tooltip, type TooltipSide } from './tooltip';\n\nexport type ButtonVariant = 'default' | 'outline' | 'ghost' | 'destructive';\n\n/**\n * Three text tiers (`sm` / `md` / `lg`) plus a square icon-only tier for each.\n * `default` and `icon` are kept as aliases of `md` / `icon-md`.\n */\nexport type ButtonSize =\n | 'sm'\n | 'md'\n | 'lg'\n | 'icon-sm'\n | 'icon-md'\n | 'icon-lg'\n | 'default'\n | 'icon';\n\nconst variants: Record<ButtonVariant, string> = {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n outline: 'border border-border bg-transparent hover:bg-muted',\n ghost: 'hover:bg-muted',\n destructive: 'text-destructive hover:bg-destructive/10',\n};\n\nconst sizes: Record<ButtonSize, string> = {\n sm: 'h-8 px-3 gap-1.5 text-xs [&_svg]:size-3.5',\n md: 'h-9 px-4 gap-2 text-sm [&_svg]:size-4',\n lg: 'h-11 px-6 gap-2.5 text-base [&_svg]:size-5',\n 'icon-sm': 'size-8 [&_svg]:size-3.5',\n 'icon-md': 'size-9 [&_svg]:size-4',\n 'icon-lg': 'size-11 [&_svg]:size-5',\n default: 'h-9 px-4 gap-2 text-sm [&_svg]:size-4',\n icon: 'size-9 [&_svg]:size-4',\n};\n\nconst ICON_ONLY: ReadonlySet<ButtonSize> = new Set<ButtonSize>([\n 'icon-sm',\n 'icon-md',\n 'icon-lg',\n 'icon',\n]);\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /**\n * Swap the leading icon for a spinner, disable the button and mark it\n * `aria-busy`. The label stays put so the button does not resize.\n */\n loading?: boolean;\n /** Announced (and shown, on a text-tier button) in place of the label while `loading`. */\n loadingText?: string;\n /** Leading icon — or the only content, on an `icon-*` tier. */\n icon?: React.ReactNode;\n /** Put `icon` after the label instead of before it. */\n iconPosition?: 'left' | 'right';\n /**\n * Hover/focus hint. On an icon-only button it also becomes the accessible\n * name when no `aria-label` is given; otherwise it is wired up as\n * `aria-describedby`.\n */\n tooltip?: React.ReactNode;\n tooltipSide?: TooltipSide;\n}\n\n/**\n * The design-system button: four variants, three size tiers (icon-only twins of\n * each), an icon slot, a loading state and an optional tooltip.\n *\n * `type` defaults to `\"button\"` so dropping one inside a form never submits it\n * by accident — pass `type=\"submit\"` when that is what you want.\n */\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n className,\n variant = 'default',\n size = 'default',\n type = 'button',\n loading = false,\n loadingText,\n icon,\n iconPosition = 'left',\n tooltip,\n tooltipSide = 'top',\n disabled,\n children,\n ...props\n },\n ref,\n) {\n const iconOnly = ICON_ONLY.has(size);\n const showsLoadingText = loading && !!loadingText && !iconOnly;\n const label = showsLoadingText ? loadingText : children;\n const glyph = loading ? <Loader2 className=\"animate-spin\" aria-hidden=\"true\" /> : icon;\n\n // An icon-only button has no text to name it: fall back to the tooltip when\n // it is a plain string, so `<Button size=\"icon\" icon={<X />} tooltip=\"Close\" />`\n // is announced as \"Close\" rather than as an unlabelled button. When it serves\n // as the name it must not also describe the button — that reads it twice.\n const tooltipIsName = iconOnly && typeof tooltip === 'string' && !props['aria-label'];\n const ariaLabel = props['aria-label'] ?? (tooltipIsName ? (tooltip as string) : undefined);\n\n const button = (\n <button\n {...props}\n ref={ref}\n type={type}\n aria-label={ariaLabel}\n aria-busy={loading || undefined}\n disabled={disabled || loading}\n className={cn(\n 'inline-flex shrink-0 items-center justify-center rounded-lg font-medium whitespace-nowrap transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 [&_svg]:shrink-0',\n // A disabled button normally stops taking pointer events at all — but a\n // tooltip is exactly what you want to read when a button is disabled or\n // busy, so keep it hoverable in that case.\n tooltip ? 'disabled:cursor-not-allowed' : 'disabled:pointer-events-none',\n variants[variant],\n sizes[size],\n className,\n )}\n >\n {glyph && iconPosition === 'left' && glyph}\n {label}\n {glyph && iconPosition === 'right' && glyph}\n {/* Announce the busy state — unless the visible label already says it. */}\n {loading && !showsLoadingText && (\n <span className=\"sr-only\" role=\"status\">\n {loadingText ?? 'Loading'}\n </span>\n )}\n </button>\n );\n\n if (!tooltip) return button;\n\n return (\n <Tooltip content={tooltip} side={tooltipSide} describes={!tooltipIsName}>\n {button}\n </Tooltip>\n );\n});\n"],"names":["variants","sizes","ICON_ONLY","Button","React","className","variant","size","type","loading","loadingText","icon","iconPosition","tooltip","tooltipSide","disabled","children","props","ref","iconOnly","showsLoadingText","label","glyph","jsx","Loader2","tooltipIsName","ariaLabel","button","jsxs","cn","Tooltip"],"mappings":";;;;;AAsBA,MAAMA,IAA0C;AAAA,EAC9C,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,aAAa;AACf,GAEMC,IAAoC;AAAA,EACxC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR,GAEMC,wBAAyC,IAAgB;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC,GAgCYC,IAASC,EAAM,WAA2C,SACrE;AAAA,EACE,WAAAC;AAAA,EACA,SAAAC,IAAU;AAAA,EACV,MAAAC,IAAO;AAAA,EACP,MAAAC,IAAO;AAAA,EACP,SAAAC,IAAU;AAAA,EACV,aAAAC;AAAA,EACA,MAAAC;AAAA,EACA,cAAAC,IAAe;AAAA,EACf,SAAAC;AAAA,EACA,aAAAC,IAAc;AAAA,EACd,UAAAC;AAAA,EACA,UAAAC;AAAA,EACA,GAAGC;AACL,GACAC,GACA;AACA,QAAMC,IAAWjB,EAAU,IAAIK,CAAI,GAC7Ba,IAAmBX,KAAW,CAAC,CAACC,KAAe,CAACS,GAChDE,IAAQD,IAAmBV,IAAcM,GACzCM,IAAQb,IAAU,gBAAAc,EAACC,GAAA,EAAQ,WAAU,gBAAe,eAAY,QAAO,IAAKb,GAM5Ec,IAAgBN,KAAY,OAAON,KAAY,YAAY,CAACI,EAAM,YAAY,GAC9ES,IAAYT,EAAM,YAAY,MAAMQ,IAAiBZ,IAAqB,SAE1Ec,IACJ,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAGX;AAAA,MACJ,KAAAC;AAAA,MACA,MAAAV;AAAA,MACA,cAAYkB;AAAA,MACZ,aAAWjB,KAAW;AAAA,MACtB,UAAUM,KAAYN;AAAA,MACtB,WAAWoB;AAAA,QACT;AAAA;AAAA;AAAA;AAAA,QAIAhB,IAAU,gCAAgC;AAAA,QAC1Cb,EAASM,CAAO;AAAA,QAChBL,EAAMM,CAAI;AAAA,QACVF;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAAiB,KAASV,MAAiB,UAAUU;AAAA,QACpCD;AAAA,QACAC,KAASV,MAAiB,WAAWU;AAAA,QAErCb,KAAW,CAACW,KACX,gBAAAG,EAAC,QAAA,EAAK,WAAU,WAAU,MAAK,UAC5B,UAAAb,KAAe,UAAA,CAClB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAKN,SAAKG,IAGH,gBAAAU,EAACO,KAAQ,SAASjB,GAAS,MAAMC,GAAa,WAAW,CAACW,GACvD,UAAAE,EAAA,CACH,IALmBA;AAOvB,CAAC;"}
@@ -0,0 +1,5 @@
1
+ export { Button } from './button';
2
+ export type { ButtonProps, ButtonVariant, ButtonSize } from './button';
3
+ export { Tooltip } from './tooltip';
4
+ export type { TooltipProps, TooltipSide } from './tooltip';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/button/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+ export type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
3
+ export interface TooltipProps {
4
+ content: React.ReactNode;
5
+ side?: TooltipSide;
6
+ /**
7
+ * Wire the bubble to the trigger with `aria-describedby`. Pass `false` when the
8
+ * tooltip text already *is* the trigger's accessible name (an icon-only button
9
+ * that took its `aria-label` from it) — a screen reader would otherwise read it
10
+ * twice.
11
+ */
12
+ describes?: boolean;
13
+ children: React.ReactElement<{
14
+ ref?: React.Ref<HTMLElement>;
15
+ 'aria-describedby'?: string;
16
+ onPointerEnter?: React.PointerEventHandler;
17
+ onPointerLeave?: React.PointerEventHandler;
18
+ onFocus?: React.FocusEventHandler;
19
+ onBlur?: React.FocusEventHandler;
20
+ }>;
21
+ }
22
+ /**
23
+ * A hover/focus hint for a single trigger element.
24
+ *
25
+ * Rendered in a portal and positioned from the trigger's box, so it escapes any
26
+ * `overflow: hidden` ancestor without a wrapper element distorting the layout.
27
+ * Follows the WAI-ARIA tooltip pattern: `role="tooltip"`, shown on hover *and*
28
+ * keyboard focus, dismissed with Escape.
29
+ */
30
+ export declare function Tooltip({ content, side, describes, children }: TooltipProps): React.JSX.Element;
31
+ //# sourceMappingURL=tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../src/components/button/tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAwB9D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC;QAC3B,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,cAAc,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC;QAC3C,cAAc,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC;QAC3C,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC;QAClC,MAAM,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC;KAClC,CAAC,CAAC;CACJ;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,EAAE,OAAO,EAAE,IAAY,EAAE,SAAgB,EAAE,QAAQ,EAAE,EAAE,YAAY,qBAsF1F"}
@@ -0,0 +1,83 @@
1
+ import { jsxs as b, Fragment as y, jsx as h } from "react/jsx-runtime";
2
+ import * as r from "react";
3
+ import { createPortal as E } from "react-dom";
4
+ const a = 8, L = {
5
+ top: "translate(-50%, -100%)",
6
+ bottom: "translate(-50%, 0)",
7
+ left: "translate(-100%, -50%)",
8
+ right: "translate(0, -50%)"
9
+ };
10
+ function P(o, c) {
11
+ switch (c) {
12
+ case "bottom":
13
+ return { top: o.bottom + a, left: o.left + o.width / 2 };
14
+ case "left":
15
+ return { top: o.top + o.height / 2, left: o.left - a };
16
+ case "right":
17
+ return { top: o.top + o.height / 2, left: o.right + a };
18
+ default:
19
+ return { top: o.top - a, left: o.left + o.width / 2 };
20
+ }
21
+ }
22
+ function k({ content: o, side: c = "top", describes: w = !0, children: v }) {
23
+ const f = r.useId(), p = r.useRef(null), u = r.useRef(void 0), [l, d] = r.useState(null), i = r.useCallback(() => {
24
+ clearTimeout(u.current), d(null);
25
+ }, []), m = r.useCallback((e) => {
26
+ clearTimeout(u.current), u.current = setTimeout(() => {
27
+ var n;
28
+ const t = (n = p.current) == null ? void 0 : n.getBoundingClientRect();
29
+ t && d(P(t, c));
30
+ }, e);
31
+ }, [c]);
32
+ r.useEffect(() => () => clearTimeout(u.current), []), r.useEffect(() => {
33
+ if (!l) return;
34
+ const e = (t) => {
35
+ t.key === "Escape" && i();
36
+ };
37
+ return document.addEventListener("keydown", e), window.addEventListener("scroll", i, !0), window.addEventListener("resize", i), () => {
38
+ document.removeEventListener("keydown", e), window.removeEventListener("scroll", i, !0), window.removeEventListener("resize", i);
39
+ };
40
+ }, [l, i]);
41
+ const s = v, g = r.cloneElement(s, {
42
+ ref: (e) => {
43
+ p.current = e;
44
+ const t = s.ref;
45
+ typeof t == "function" ? t(e) : t && typeof t == "object" && (t.current = e);
46
+ },
47
+ "aria-describedby": l && w ? f : s.props["aria-describedby"],
48
+ onPointerEnter: (e) => {
49
+ var t, n;
50
+ (n = (t = s.props).onPointerEnter) == null || n.call(t, e), e.pointerType !== "touch" && m(150);
51
+ },
52
+ onPointerLeave: (e) => {
53
+ var t, n;
54
+ (n = (t = s.props).onPointerLeave) == null || n.call(t, e), i();
55
+ },
56
+ onFocus: (e) => {
57
+ var t, n;
58
+ (n = (t = s.props).onFocus) == null || n.call(t, e), e.target.matches(":focus-visible") && m(0);
59
+ },
60
+ onBlur: (e) => {
61
+ var t, n;
62
+ (n = (t = s.props).onBlur) == null || n.call(t, e), i();
63
+ }
64
+ });
65
+ return /* @__PURE__ */ b(y, { children: [
66
+ g,
67
+ l && typeof document < "u" && E(
68
+ /* @__PURE__ */ h(
69
+ "div",
70
+ {
71
+ className: "ds-tooltip-anchor",
72
+ style: { top: l.top, left: l.left, transform: L[c] },
73
+ children: /* @__PURE__ */ h("span", { id: f, role: "tooltip", className: "ds-tooltip", children: o })
74
+ }
75
+ ),
76
+ document.body
77
+ )
78
+ ] });
79
+ }
80
+ export {
81
+ k as Tooltip
82
+ };
83
+ //# sourceMappingURL=tooltip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.js","sources":["../../../src/components/button/tooltip.tsx"],"sourcesContent":["import * as React from 'react';\nimport { createPortal } from 'react-dom';\n\nexport type TooltipSide = 'top' | 'bottom' | 'left' | 'right';\n\nconst GAP = 8;\n\nconst TRANSFORMS: Record<TooltipSide, string> = {\n top: 'translate(-50%, -100%)',\n bottom: 'translate(-50%, 0)',\n left: 'translate(-100%, -50%)',\n right: 'translate(0, -50%)',\n};\n\nfunction anchorPoint(rect: DOMRect, side: TooltipSide) {\n switch (side) {\n case 'bottom':\n return { top: rect.bottom + GAP, left: rect.left + rect.width / 2 };\n case 'left':\n return { top: rect.top + rect.height / 2, left: rect.left - GAP };\n case 'right':\n return { top: rect.top + rect.height / 2, left: rect.right + GAP };\n default:\n return { top: rect.top - GAP, left: rect.left + rect.width / 2 };\n }\n}\n\nexport interface TooltipProps {\n content: React.ReactNode;\n side?: TooltipSide;\n /**\n * Wire the bubble to the trigger with `aria-describedby`. Pass `false` when the\n * tooltip text already *is* the trigger's accessible name (an icon-only button\n * that took its `aria-label` from it) — a screen reader would otherwise read it\n * twice.\n */\n describes?: boolean;\n children: React.ReactElement<{\n ref?: React.Ref<HTMLElement>;\n 'aria-describedby'?: string;\n onPointerEnter?: React.PointerEventHandler;\n onPointerLeave?: React.PointerEventHandler;\n onFocus?: React.FocusEventHandler;\n onBlur?: React.FocusEventHandler;\n }>;\n}\n\n/**\n * A hover/focus hint for a single trigger element.\n *\n * Rendered in a portal and positioned from the trigger's box, so it escapes any\n * `overflow: hidden` ancestor without a wrapper element distorting the layout.\n * Follows the WAI-ARIA tooltip pattern: `role=\"tooltip\"`, shown on hover *and*\n * keyboard focus, dismissed with Escape.\n */\nexport function Tooltip({ content, side = 'top', describes = true, children }: TooltipProps) {\n const id = React.useId();\n const triggerRef = React.useRef<HTMLElement | null>(null);\n const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const [point, setPoint] = React.useState<{ top: number; left: number } | null>(null);\n\n const hide = React.useCallback(() => {\n clearTimeout(timer.current);\n setPoint(null);\n }, []);\n\n const show = React.useCallback((delay: number) => {\n clearTimeout(timer.current);\n timer.current = setTimeout(() => {\n const rect = triggerRef.current?.getBoundingClientRect();\n if (rect) setPoint(anchorPoint(rect, side));\n }, delay);\n }, [side]);\n\n React.useEffect(() => () => clearTimeout(timer.current), []);\n\n // While it is up: Escape dismisses it, and any scroll or resize invalidates the\n // point it was measured from, so drop it rather than let it drift.\n React.useEffect(() => {\n if (!point) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') hide();\n };\n document.addEventListener('keydown', onKeyDown);\n window.addEventListener('scroll', hide, true);\n window.addEventListener('resize', hide);\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n window.removeEventListener('scroll', hide, true);\n window.removeEventListener('resize', hide);\n };\n }, [point, hide]);\n\n const child = children;\n const trigger = React.cloneElement(child, {\n ref: (node: HTMLElement | null) => {\n triggerRef.current = node;\n const ref = (child as unknown as { ref?: React.Ref<HTMLElement> }).ref;\n if (typeof ref === 'function') ref(node);\n else if (ref && typeof ref === 'object') (ref as React.RefObject<HTMLElement | null>).current = node;\n },\n 'aria-describedby': point && describes ? id : child.props['aria-describedby'],\n onPointerEnter: (e: React.PointerEvent) => {\n child.props.onPointerEnter?.(e);\n // Touch already fires a click; a hover bubble there is just noise.\n if (e.pointerType !== 'touch') show(150);\n },\n onPointerLeave: (e: React.PointerEvent) => {\n child.props.onPointerLeave?.(e);\n hide();\n },\n onFocus: (e: React.FocusEvent) => {\n child.props.onFocus?.(e);\n // Keyboard focus only — a mouse click focuses too, and the hover bubble\n // that is already up should not be pinned open by it.\n if (e.target.matches(':focus-visible')) show(0);\n },\n onBlur: (e: React.FocusEvent) => {\n child.props.onBlur?.(e);\n hide();\n },\n });\n\n return (\n <>\n {trigger}\n {point &&\n typeof document !== 'undefined' &&\n createPortal(\n <div\n className=\"ds-tooltip-anchor\"\n style={{ top: point.top, left: point.left, transform: TRANSFORMS[side] }}\n >\n <span id={id} role=\"tooltip\" className=\"ds-tooltip\">\n {content}\n </span>\n </div>,\n document.body,\n )}\n </>\n );\n}\n"],"names":["GAP","TRANSFORMS","anchorPoint","rect","side","Tooltip","content","describes","children","id","React","triggerRef","timer","point","setPoint","hide","show","delay","_a","onKeyDown","e","child","trigger","node","ref","_b","jsxs","Fragment","createPortal","jsx"],"mappings":";;;AAKA,MAAMA,IAAM,GAENC,IAA0C;AAAA,EAC9C,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AACT;AAEA,SAASC,EAAYC,GAAeC,GAAmB;AACrD,UAAQA,GAAA;AAAA,IACN,KAAK;AACH,aAAO,EAAE,KAAKD,EAAK,SAASH,GAAK,MAAMG,EAAK,OAAOA,EAAK,QAAQ,EAAA;AAAA,IAClE,KAAK;AACH,aAAO,EAAE,KAAKA,EAAK,MAAMA,EAAK,SAAS,GAAG,MAAMA,EAAK,OAAOH,EAAA;AAAA,IAC9D,KAAK;AACH,aAAO,EAAE,KAAKG,EAAK,MAAMA,EAAK,SAAS,GAAG,MAAMA,EAAK,QAAQH,EAAA;AAAA,IAC/D;AACE,aAAO,EAAE,KAAKG,EAAK,MAAMH,GAAK,MAAMG,EAAK,OAAOA,EAAK,QAAQ,EAAA;AAAA,EAAE;AAErE;AA8BO,SAASE,EAAQ,EAAE,SAAAC,GAAS,MAAAF,IAAO,OAAO,WAAAG,IAAY,IAAM,UAAAC,KAA0B;AAC3F,QAAMC,IAAKC,EAAM,MAAA,GACXC,IAAaD,EAAM,OAA2B,IAAI,GAClDE,IAAQF,EAAM,OAAkD,MAAS,GACzE,CAACG,GAAOC,CAAQ,IAAIJ,EAAM,SAA+C,IAAI,GAE7EK,IAAOL,EAAM,YAAY,MAAM;AACnC,iBAAaE,EAAM,OAAO,GAC1BE,EAAS,IAAI;AAAA,EACf,GAAG,CAAA,CAAE,GAECE,IAAON,EAAM,YAAY,CAACO,MAAkB;AAChD,iBAAaL,EAAM,OAAO,GAC1BA,EAAM,UAAU,WAAW,MAAM;;AAC/B,YAAMT,KAAOe,IAAAP,EAAW,YAAX,gBAAAO,EAAoB;AACjC,MAAIf,KAAMW,EAASZ,EAAYC,GAAMC,CAAI,CAAC;AAAA,IAC5C,GAAGa,CAAK;AAAA,EACV,GAAG,CAACb,CAAI,CAAC;AAET,EAAAM,EAAM,UAAU,MAAM,MAAM,aAAaE,EAAM,OAAO,GAAG,EAAE,GAI3DF,EAAM,UAAU,MAAM;AACpB,QAAI,CAACG,EAAO;AACZ,UAAMM,IAAY,CAACC,MAAqB;AACtC,MAAIA,EAAE,QAAQ,YAAUL,EAAA;AAAA,IAC1B;AACA,oBAAS,iBAAiB,WAAWI,CAAS,GAC9C,OAAO,iBAAiB,UAAUJ,GAAM,EAAI,GAC5C,OAAO,iBAAiB,UAAUA,CAAI,GAC/B,MAAM;AACX,eAAS,oBAAoB,WAAWI,CAAS,GACjD,OAAO,oBAAoB,UAAUJ,GAAM,EAAI,GAC/C,OAAO,oBAAoB,UAAUA,CAAI;AAAA,IAC3C;AAAA,EACF,GAAG,CAACF,GAAOE,CAAI,CAAC;AAEhB,QAAMM,IAAQb,GACRc,IAAUZ,EAAM,aAAaW,GAAO;AAAA,IACxC,KAAK,CAACE,MAA6B;AACjC,MAAAZ,EAAW,UAAUY;AACrB,YAAMC,IAAOH,EAAsD;AACnE,MAAI,OAAOG,KAAQ,aAAYA,EAAID,CAAI,IAC9BC,KAAO,OAAOA,KAAQ,aAAWA,EAA4C,UAAUD;AAAA,IAClG;AAAA,IACA,oBAAoBV,KAASN,IAAYE,IAAKY,EAAM,MAAM,kBAAkB;AAAA,IAC5E,gBAAgB,CAAC,MAA0B;;AACzC,OAAAI,KAAAP,IAAAG,EAAM,OAAM,mBAAZ,QAAAI,EAAA,KAAAP,GAA6B,IAEzB,EAAE,gBAAgB,WAASF,EAAK,GAAG;AAAA,IACzC;AAAA,IACA,gBAAgB,CAAC,MAA0B;;AACzC,OAAAS,KAAAP,IAAAG,EAAM,OAAM,mBAAZ,QAAAI,EAAA,KAAAP,GAA6B,IAC7BH,EAAA;AAAA,IACF;AAAA,IACA,SAAS,CAAC,MAAwB;;AAChC,OAAAU,KAAAP,IAAAG,EAAM,OAAM,YAAZ,QAAAI,EAAA,KAAAP,GAAsB,IAGlB,EAAE,OAAO,QAAQ,gBAAgB,OAAQ,CAAC;AAAA,IAChD;AAAA,IACA,QAAQ,CAAC,MAAwB;;AAC/B,OAAAO,KAAAP,IAAAG,EAAM,OAAM,WAAZ,QAAAI,EAAA,KAAAP,GAAqB,IACrBH,EAAA;AAAA,IACF;AAAA,EAAA,CACD;AAED,SACE,gBAAAW,EAAAC,GAAA,EACG,UAAA;AAAA,IAAAL;AAAA,IACAT,KACC,OAAO,WAAa,OACpBe;AAAA,MACE,gBAAAC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,EAAE,KAAKhB,EAAM,KAAK,MAAMA,EAAM,MAAM,WAAWZ,EAAWG,CAAI,EAAA;AAAA,UAErE,4BAAC,QAAA,EAAK,IAAAK,GAAQ,MAAK,WAAU,WAAU,cACpC,UAAAH,EAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,MAEF,SAAS;AAAA,IAAA;AAAA,EACX,GACJ;AAEJ;"}
@@ -0,0 +1,52 @@
1
+ import { ReactNode } from 'react';
2
+ export interface ChangelogEntry {
3
+ version: string;
4
+ date?: string;
5
+ title?: string;
6
+ changes: string[];
7
+ sha?: string;
8
+ }
9
+ export interface ChangelogProps {
10
+ /**
11
+ * Supply entries directly (controlled). When set, the hosted SDK is not
12
+ * loaded — pair with `onLoadMore` / `hasMore` / `loading` to page a long
13
+ * history. Omit to auto-load from the changelog-widget SDK.
14
+ */
15
+ entries?: ChangelogEntry[];
16
+ /** URL of the JSONL changelog (SDK mode). Default: /changelog.jsonl */
17
+ url?: string;
18
+ /** URL of the SDK script (SDK mode). Default: the hosted changelog script. */
19
+ sdkUrl?: string;
20
+ /** Modal heading. Default: "Changelog". */
21
+ title?: string;
22
+ /**
23
+ * Custom trigger. Receives `open` (show the modal) and `hasUpdate` (a newer
24
+ * version is available). If omitted, a plain "Changelog" text button renders.
25
+ */
26
+ trigger?: (args: {
27
+ open: () => void;
28
+ hasUpdate: boolean;
29
+ }) => ReactNode;
30
+ /** Controlled mode: append the next page (used by the virtual list's tail). */
31
+ onLoadMore?: () => void;
32
+ /** Controlled mode: more pages remain. */
33
+ hasMore?: boolean;
34
+ /** Controlled mode: a page load is in flight. */
35
+ loading?: boolean;
36
+ /**
37
+ * Controlled "new version" reload toast. Set to an entry to show the toast
38
+ * for it — useful when you detect an update yourself (e.g. in controlled
39
+ * mode, where the SDK watcher isn't running). Set to `null`/omit to hide it.
40
+ */
41
+ newVersion?: ChangelogEntry | null;
42
+ /** Called when the user dismisses the "new version" toast. */
43
+ onDismissNewVersion?: () => void;
44
+ }
45
+ /**
46
+ * A changelog trigger + modal + "new version" reload toast, styled from the
47
+ * design tokens. The entry list is a {@link VirtualList}, so a long history
48
+ * scrolls cheaply and can lazily page in via `onLoadMore`. Data comes from the
49
+ * headless changelog-widget SDK by default, or pass `entries` directly.
50
+ */
51
+ export declare function Changelog({ entries, url, sdkUrl, title, trigger, onLoadMore, hasMore, loading, newVersion, onDismissNewVersion, }: ChangelogProps): import("react").JSX.Element;
52
+ //# sourceMappingURL=changelog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../../src/components/changelog/changelog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQpE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA2CD,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,IAAI,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,KAAK,SAAS,CAAC;IACxE,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,8DAA8D;IAC9D,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,GAAuB,EACvB,MAAgB,EAChB,KAAmB,EACnB,OAAO,EACP,UAAU,EACV,OAAe,EACf,OAAe,EACf,UAAiB,EACjB,mBAAmB,GACpB,EAAE,cAAc,+BAqIhB"}
@@ -0,0 +1,164 @@
1
+ import { jsxs as n, Fragment as z, jsx as e } from "react/jsx-runtime";
2
+ import { useState as b, useRef as j, useEffect as k } from "react";
3
+ import { X as A, Sparkles as T } from "lucide-react";
4
+ import { VirtualList as D } from "../virtual-list/virtual-list.js";
5
+ const F = "https://changelog-widget.dev.gabvdl.xyz/script.js", G = "/changelog.jsonl";
6
+ function v() {
7
+ return window.Changelog;
8
+ }
9
+ function I(t) {
10
+ const o = v();
11
+ return o ? Promise.resolve(o) : new Promise((a, s) => {
12
+ const c = () => {
13
+ const d = v();
14
+ d ? a(d) : s(new Error("SDK missing"));
15
+ }, i = document.querySelector(`script[src="${t}"]`);
16
+ if (i) {
17
+ i.addEventListener("load", c, { once: !0 }), i.addEventListener("error", () => s(new Error("SDK failed")), { once: !0 });
18
+ const d = v();
19
+ d && a(d);
20
+ return;
21
+ }
22
+ const l = document.createElement("script");
23
+ l.src = t, l.async = !0, l.addEventListener("load", c, { once: !0 }), l.addEventListener("error", () => s(new Error("SDK failed")), { once: !0 }), document.head.appendChild(l);
24
+ });
25
+ }
26
+ function X({
27
+ entries: t,
28
+ url: o = G,
29
+ sdkUrl: a = F,
30
+ title: s = "Changelog",
31
+ trigger: c,
32
+ onLoadMore: i,
33
+ hasMore: l = !1,
34
+ loading: d = !1,
35
+ newVersion: C = null,
36
+ onDismissNewVersion: f
37
+ }) {
38
+ const h = t !== void 0, [L, y] = b([]), [x, m] = b(!1), [S, N] = b(null), w = j(() => {
39
+ }), K = h ? t : L;
40
+ k(() => {
41
+ if (h) return;
42
+ let r = !0;
43
+ return I(a).then((p) => {
44
+ r && (p.fetch(o).then((g) => r && y(g)), w.current = p.watch({
45
+ url: o,
46
+ onUpdate: (g, R) => {
47
+ r && (y(R), N(g));
48
+ }
49
+ }));
50
+ }).catch(() => {
51
+ }), () => {
52
+ r = !1, w.current();
53
+ };
54
+ }, [h, o, a]), k(() => {
55
+ if (!x) return;
56
+ const r = (p) => p.key === "Escape" && m(!1);
57
+ return document.addEventListener("keydown", r), () => document.removeEventListener("keydown", r);
58
+ }, [x]);
59
+ const u = C ?? S, E = u !== null, U = () => {
60
+ N(null), f == null || f();
61
+ };
62
+ return /* @__PURE__ */ n(z, { children: [
63
+ c ? c({ open: () => m(!0), hasUpdate: E }) : /* @__PURE__ */ n(
64
+ "button",
65
+ {
66
+ type: "button",
67
+ onClick: () => m(!0),
68
+ className: "relative inline-flex items-center text-sm font-medium text-muted-foreground transition-colors hover:text-foreground",
69
+ children: [
70
+ "Changelog",
71
+ E && /* @__PURE__ */ e("span", { className: "ml-1.5 h-1.5 w-1.5 rounded-full bg-primary", "aria-hidden": !0 })
72
+ ]
73
+ }
74
+ ),
75
+ x && /* @__PURE__ */ e(
76
+ "div",
77
+ {
78
+ className: "fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm",
79
+ onClick: (r) => r.target === r.currentTarget && m(!1),
80
+ children: /* @__PURE__ */ n("div", { className: "flex max-h-[80vh] w-full max-w-md flex-col overflow-hidden rounded-2xl border border-border bg-card shadow-2xl", children: [
81
+ /* @__PURE__ */ n("div", { className: "flex items-center gap-2 border-b border-border px-5 py-4", children: [
82
+ /* @__PURE__ */ e("h2", { className: "flex-1 text-base font-bold text-foreground", children: s }),
83
+ /* @__PURE__ */ e(
84
+ "button",
85
+ {
86
+ type: "button",
87
+ onClick: () => m(!1),
88
+ "aria-label": "Close",
89
+ className: "grid h-8 w-8 place-items-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
90
+ children: /* @__PURE__ */ e(A, { className: "h-4 w-4" })
91
+ }
92
+ )
93
+ ] }),
94
+ /* @__PURE__ */ e(
95
+ D,
96
+ {
97
+ items: K,
98
+ className: "min-h-0 flex-1 px-5",
99
+ estimateSize: 120,
100
+ overscan: 4,
101
+ getItemKey: (r) => r.version,
102
+ onEndReached: i,
103
+ hasMore: l,
104
+ loading: d,
105
+ emptyState: /* @__PURE__ */ e("p", { className: "py-6 text-center text-sm text-muted-foreground", children: "No changelog yet." }),
106
+ renderItem: (r) => /* @__PURE__ */ e(O, { entry: r })
107
+ }
108
+ )
109
+ ] })
110
+ }
111
+ ),
112
+ u && /* @__PURE__ */ e("div", { className: "fixed left-1/2 top-3 z-[9999] w-[min(92vw,400px)] -translate-x-1/2", children: /* @__PURE__ */ n("div", { className: "flex items-start gap-3 rounded-2xl border border-primary/40 bg-card p-3.5 shadow-xl", children: [
113
+ /* @__PURE__ */ e("span", { className: "grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ e(T, { className: "h-4 w-4" }) }),
114
+ /* @__PURE__ */ n("div", { className: "min-w-0 flex-1", children: [
115
+ /* @__PURE__ */ n("p", { className: "font-mono text-[10px] font-bold uppercase tracking-[0.14em] text-primary", children: [
116
+ "New version · v",
117
+ u.version
118
+ ] }),
119
+ /* @__PURE__ */ e("p", { className: "mt-0.5 text-sm text-foreground", children: u.title || u.changes[0] || "A new version is available." }),
120
+ /* @__PURE__ */ n("div", { className: "mt-2 flex items-center gap-2", children: [
121
+ /* @__PURE__ */ e(
122
+ "button",
123
+ {
124
+ type: "button",
125
+ onClick: () => location.reload(),
126
+ className: "rounded-lg bg-primary px-3 py-1 text-xs font-bold text-primary-foreground transition-opacity hover:opacity-90",
127
+ children: "Reload"
128
+ }
129
+ ),
130
+ /* @__PURE__ */ e(
131
+ "button",
132
+ {
133
+ type: "button",
134
+ onClick: U,
135
+ className: "rounded-lg px-2 py-1 text-xs font-semibold text-muted-foreground transition-colors hover:text-foreground",
136
+ children: "Dismiss"
137
+ }
138
+ )
139
+ ] })
140
+ ] })
141
+ ] }) })
142
+ ] });
143
+ }
144
+ function O({ entry: t }) {
145
+ const o = t.title && !(t.changes.length === 1 && t.changes[0] === t.title);
146
+ return /* @__PURE__ */ n("div", { className: "border-b border-border py-4 last:border-none", children: [
147
+ /* @__PURE__ */ n("div", { className: "mb-1.5 flex items-baseline gap-2", children: [
148
+ /* @__PURE__ */ n("span", { className: "rounded-md bg-primary/10 px-2 py-0.5 font-mono text-[11px] font-bold text-primary", children: [
149
+ "v",
150
+ t.version
151
+ ] }),
152
+ t.date && /* @__PURE__ */ e("span", { className: "text-xs text-muted-foreground", children: t.date })
153
+ ] }),
154
+ o && /* @__PURE__ */ e("p", { className: "mb-1.5 text-sm font-semibold text-foreground", children: t.title }),
155
+ /* @__PURE__ */ e("ul", { className: "space-y-1", children: t.changes.map((a, s) => /* @__PURE__ */ n("li", { className: "relative pl-4 text-[13px] leading-snug text-muted-foreground", children: [
156
+ /* @__PURE__ */ e("span", { className: "absolute left-0 top-2 h-1 w-1 rounded-full bg-primary" }),
157
+ a
158
+ ] }, s)) })
159
+ ] });
160
+ }
161
+ export {
162
+ X as Changelog
163
+ };
164
+ //# sourceMappingURL=changelog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changelog.js","sources":["../../../src/components/changelog/changelog.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Sparkles, X } from 'lucide-react';\n\nimport { VirtualList } from '../virtual-list';\n\nconst SDK_URL = 'https://changelog-widget.dev.gabvdl.xyz/script.js';\nconst DEFAULT_CHANGELOG = '/changelog.jsonl';\n\nexport interface ChangelogEntry {\n version: string;\n date?: string;\n title?: string;\n changes: string[];\n sha?: string;\n}\n\ninterface ChangelogSDK {\n fetch(url?: string): Promise<ChangelogEntry[]>;\n latest(entries: ChangelogEntry[]): ChangelogEntry | null;\n watch(opts: {\n url?: string;\n intervalMs?: number;\n onUpdate: (latest: ChangelogEntry, entries: ChangelogEntry[]) => void;\n }): () => void;\n}\n\n/** Read window.Changelog without a global augmentation (keeps this drop-in). */\nfunction globalSdk(): ChangelogSDK | undefined {\n return (window as unknown as { Changelog?: ChangelogSDK }).Changelog;\n}\n\n/** Load the SDK script once (idempotent) and resolve window.Changelog. */\nfunction loadSdk(src: string): Promise<ChangelogSDK> {\n const present = globalSdk();\n if (present) return Promise.resolve(present);\n return new Promise((resolve, reject) => {\n const done = () => {\n const sdk = globalSdk();\n sdk ? resolve(sdk) : reject(new Error('SDK missing'));\n };\n const existing = document.querySelector<HTMLScriptElement>(`script[src=\"${src}\"]`);\n if (existing) {\n existing.addEventListener('load', done, { once: true });\n existing.addEventListener('error', () => reject(new Error('SDK failed')), { once: true });\n const sdk = globalSdk();\n if (sdk) resolve(sdk);\n return;\n }\n const s = document.createElement('script');\n s.src = src;\n s.async = true;\n s.addEventListener('load', done, { once: true });\n s.addEventListener('error', () => reject(new Error('SDK failed')), { once: true });\n document.head.appendChild(s);\n });\n}\n\nexport interface ChangelogProps {\n /**\n * Supply entries directly (controlled). When set, the hosted SDK is not\n * loaded — pair with `onLoadMore` / `hasMore` / `loading` to page a long\n * history. Omit to auto-load from the changelog-widget SDK.\n */\n entries?: ChangelogEntry[];\n /** URL of the JSONL changelog (SDK mode). Default: /changelog.jsonl */\n url?: string;\n /** URL of the SDK script (SDK mode). Default: the hosted changelog script. */\n sdkUrl?: string;\n /** Modal heading. Default: \"Changelog\". */\n title?: string;\n /**\n * Custom trigger. Receives `open` (show the modal) and `hasUpdate` (a newer\n * version is available). If omitted, a plain \"Changelog\" text button renders.\n */\n trigger?: (args: { open: () => void; hasUpdate: boolean }) => ReactNode;\n /** Controlled mode: append the next page (used by the virtual list's tail). */\n onLoadMore?: () => void;\n /** Controlled mode: more pages remain. */\n hasMore?: boolean;\n /** Controlled mode: a page load is in flight. */\n loading?: boolean;\n /**\n * Controlled \"new version\" reload toast. Set to an entry to show the toast\n * for it — useful when you detect an update yourself (e.g. in controlled\n * mode, where the SDK watcher isn't running). Set to `null`/omit to hide it.\n */\n newVersion?: ChangelogEntry | null;\n /** Called when the user dismisses the \"new version\" toast. */\n onDismissNewVersion?: () => void;\n}\n\n/**\n * A changelog trigger + modal + \"new version\" reload toast, styled from the\n * design tokens. The entry list is a {@link VirtualList}, so a long history\n * scrolls cheaply and can lazily page in via `onLoadMore`. Data comes from the\n * headless changelog-widget SDK by default, or pass `entries` directly.\n */\nexport function Changelog({\n entries,\n url = DEFAULT_CHANGELOG,\n sdkUrl = SDK_URL,\n title = 'Changelog',\n trigger,\n onLoadMore,\n hasMore = false,\n loading = false,\n newVersion = null,\n onDismissNewVersion,\n}: ChangelogProps) {\n const controlled = entries !== undefined;\n const [loaded, setLoaded] = useState<ChangelogEntry[]>([]);\n const [open, setOpen] = useState(false);\n const [update, setUpdate] = useState<ChangelogEntry | null>(null);\n const stopRef = useRef<() => void>(() => {});\n\n const data = controlled ? entries : loaded;\n\n useEffect(() => {\n if (controlled) return;\n let alive = true;\n loadSdk(sdkUrl)\n .then((sdk) => {\n if (!alive) return;\n void sdk.fetch(url).then((e) => alive && setLoaded(e));\n stopRef.current = sdk.watch({\n url,\n onUpdate: (latest, all) => {\n if (!alive) return;\n setLoaded(all);\n setUpdate(latest);\n },\n });\n })\n .catch(() => {});\n return () => {\n alive = false;\n stopRef.current();\n };\n }, [controlled, url, sdkUrl]);\n\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => e.key === 'Escape' && setOpen(false);\n document.addEventListener('keydown', onKey);\n return () => document.removeEventListener('keydown', onKey);\n }, [open]);\n\n // The controlled `newVersion` prop takes precedence over the SDK watcher's\n // own detected update, so a consumer can drive the reload toast directly.\n const activeUpdate = newVersion ?? update;\n const hasUpdate = activeUpdate !== null;\n const dismissUpdate = () => {\n setUpdate(null);\n onDismissNewVersion?.();\n };\n\n return (\n <>\n {trigger ? (\n trigger({ open: () => setOpen(true), hasUpdate })\n ) : (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"relative inline-flex items-center text-sm font-medium text-muted-foreground transition-colors hover:text-foreground\"\n >\n Changelog\n {hasUpdate && (\n <span className=\"ml-1.5 h-1.5 w-1.5 rounded-full bg-primary\" aria-hidden />\n )}\n </button>\n )}\n\n {open && (\n <div\n className=\"fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm\"\n onClick={(e) => e.target === e.currentTarget && setOpen(false)}\n >\n <div className=\"flex max-h-[80vh] w-full max-w-md flex-col overflow-hidden rounded-2xl border border-border bg-card shadow-2xl\">\n <div className=\"flex items-center gap-2 border-b border-border px-5 py-4\">\n <h2 className=\"flex-1 text-base font-bold text-foreground\">{title}</h2>\n <button\n type=\"button\"\n onClick={() => setOpen(false)}\n aria-label=\"Close\"\n className=\"grid h-8 w-8 place-items-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n <VirtualList\n items={data}\n className=\"min-h-0 flex-1 px-5\"\n estimateSize={120}\n overscan={4}\n getItemKey={(e) => e.version}\n onEndReached={onLoadMore}\n hasMore={hasMore}\n loading={loading}\n emptyState={<p className=\"py-6 text-center text-sm text-muted-foreground\">No changelog yet.</p>}\n renderItem={(e) => <Entry entry={e} />}\n />\n </div>\n </div>\n )}\n\n {activeUpdate && (\n <div className=\"fixed left-1/2 top-3 z-[9999] w-[min(92vw,400px)] -translate-x-1/2\">\n <div className=\"flex items-start gap-3 rounded-2xl border border-primary/40 bg-card p-3.5 shadow-xl\">\n <span className=\"grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-primary/10 text-primary\">\n <Sparkles className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-mono text-[10px] font-bold uppercase tracking-[0.14em] text-primary\">\n New version · v{activeUpdate.version}\n </p>\n <p className=\"mt-0.5 text-sm text-foreground\">\n {activeUpdate.title || activeUpdate.changes[0] || 'A new version is available.'}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => location.reload()}\n className=\"rounded-lg bg-primary px-3 py-1 text-xs font-bold text-primary-foreground transition-opacity hover:opacity-90\"\n >\n Reload\n </button>\n <button\n type=\"button\"\n onClick={dismissUpdate}\n className=\"rounded-lg px-2 py-1 text-xs font-semibold text-muted-foreground transition-colors hover:text-foreground\"\n >\n Dismiss\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </>\n );\n}\n\nfunction Entry({ entry }: { entry: ChangelogEntry }) {\n const showTitle = entry.title && !(entry.changes.length === 1 && entry.changes[0] === entry.title);\n return (\n <div className=\"border-b border-border py-4 last:border-none\">\n <div className=\"mb-1.5 flex items-baseline gap-2\">\n <span className=\"rounded-md bg-primary/10 px-2 py-0.5 font-mono text-[11px] font-bold text-primary\">\n v{entry.version}\n </span>\n {entry.date && <span className=\"text-xs text-muted-foreground\">{entry.date}</span>}\n </div>\n {showTitle && <p className=\"mb-1.5 text-sm font-semibold text-foreground\">{entry.title}</p>}\n <ul className=\"space-y-1\">\n {entry.changes.map((c, i) => (\n <li key={i} className=\"relative pl-4 text-[13px] leading-snug text-muted-foreground\">\n <span className=\"absolute left-0 top-2 h-1 w-1 rounded-full bg-primary\" />\n {c}\n </li>\n ))}\n </ul>\n </div>\n );\n}\n"],"names":["SDK_URL","DEFAULT_CHANGELOG","globalSdk","loadSdk","src","present","resolve","reject","done","sdk","existing","s","Changelog","entries","url","sdkUrl","title","trigger","onLoadMore","hasMore","loading","newVersion","onDismissNewVersion","controlled","loaded","setLoaded","useState","open","setOpen","update","setUpdate","stopRef","useRef","data","useEffect","alive","e","latest","all","onKey","activeUpdate","hasUpdate","dismissUpdate","jsxs","Fragment","jsx","X","VirtualList","Entry","Sparkles","entry","showTitle","c","i"],"mappings":"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"}