@vobs/ui 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +60 -0
  3. package/package.json +52 -110
  4. package/src/activity-rail.ts +125 -0
  5. package/src/alert.ts +65 -0
  6. package/src/assets/icons/alert-circle.svg +1 -0
  7. package/src/assets/icons/alert-triangle.svg +1 -0
  8. package/src/assets/icons/alert.d27e24.svg +1 -0
  9. package/src/assets/icons/alert.svg +1 -0
  10. package/src/assets/icons/apple.svg +1 -0
  11. package/src/assets/icons/arrow-down.svg +1 -0
  12. package/src/assets/icons/arrow-expand.svg +1 -0
  13. package/src/assets/icons/arrow-left.svg +1 -0
  14. package/src/assets/icons/arrow-minimize.svg +1 -0
  15. package/src/assets/icons/arrow-right-to-line.svg +1 -0
  16. package/src/assets/icons/arrow-right.svg +1 -0
  17. package/src/assets/icons/arrow-up-right.svg +1 -0
  18. package/src/assets/icons/arrow-up.0c0c0d.svg +1 -0
  19. package/src/assets/icons/arrow-up.svg +1 -0
  20. package/src/assets/icons/at.svg +1 -0
  21. package/src/assets/icons/atom.0c0c0d.svg +1 -0
  22. package/src/assets/icons/atom.svg +1 -0
  23. package/src/assets/icons/bar.svg +1 -0
  24. package/src/assets/icons/bell.svg +1 -0
  25. package/src/assets/icons/box.svg +1 -0
  26. package/src/assets/icons/bug.svg +1 -0
  27. package/src/assets/icons/calendar.svg +1 -0
  28. package/src/assets/icons/check-circle.33c192.svg +1 -0
  29. package/src/assets/icons/check-circle.svg +1 -0
  30. package/src/assets/icons/check.d1d3db.svg +1 -0
  31. package/src/assets/icons/check.svg +1 -0
  32. package/src/assets/icons/chevron-down.svg +1 -0
  33. package/src/assets/icons/chevron-right.svg +1 -0
  34. package/src/assets/icons/chevron-up.svg +1 -0
  35. package/src/assets/icons/circle.svg +1 -0
  36. package/src/assets/icons/clock.svg +1 -0
  37. package/src/assets/icons/cmd.svg +1 -0
  38. package/src/assets/icons/code.svg +1 -0
  39. package/src/assets/icons/columns.svg +1 -0
  40. package/src/assets/icons/copy.svg +1 -0
  41. package/src/assets/icons/cpu.svg +1 -0
  42. package/src/assets/icons/dashboard.0c0c0d.svg +1 -0
  43. package/src/assets/icons/dashboard.svg +1 -0
  44. package/src/assets/icons/dollar.svg +1 -0
  45. package/src/assets/icons/download.svg +1 -0
  46. package/src/assets/icons/edit.svg +1 -0
  47. package/src/assets/icons/extensions.svg +1 -0
  48. package/src/assets/icons/external.svg +1 -0
  49. package/src/assets/icons/eye.svg +1 -0
  50. package/src/assets/icons/file-text.svg +1 -0
  51. package/src/assets/icons/file.svg +1 -0
  52. package/src/assets/icons/files.svg +1 -0
  53. package/src/assets/icons/filter.svg +1 -0
  54. package/src/assets/icons/folder.svg +1 -0
  55. package/src/assets/icons/gear.svg +1 -0
  56. package/src/assets/icons/git.svg +1 -0
  57. package/src/assets/icons/github.svg +1 -0
  58. package/src/assets/icons/globe.svg +1 -0
  59. package/src/assets/icons/grid-2x2.svg +1 -0
  60. package/src/assets/icons/hash.svg +1 -0
  61. package/src/assets/icons/heart.svg +1 -0
  62. package/src/assets/icons/help.svg +1 -0
  63. package/src/assets/icons/home.svg +1 -0
  64. package/src/assets/icons/image.svg +1 -0
  65. package/src/assets/icons/info-circle.svg +1 -0
  66. package/src/assets/icons/info-square.svg +1 -0
  67. package/src/assets/icons/info.387bff.svg +1 -0
  68. package/src/assets/icons/info.svg +1 -0
  69. package/src/assets/icons/key.svg +1 -0
  70. package/src/assets/icons/layers.svg +1 -0
  71. package/src/assets/icons/layout.svg +1 -0
  72. package/src/assets/icons/link.svg +1 -0
  73. package/src/assets/icons/list.svg +1 -0
  74. package/src/assets/icons/lock.svg +1 -0
  75. package/src/assets/icons/log-out.svg +1 -0
  76. package/src/assets/icons/logo.svg +1 -0
  77. package/src/assets/icons/mail.svg +1 -0
  78. package/src/assets/icons/menu.svg +1 -0
  79. package/src/assets/icons/message-circle.svg +1 -0
  80. package/src/assets/icons/message-plus.svg +1 -0
  81. package/src/assets/icons/message-square.svg +1 -0
  82. package/src/assets/icons/mic.svg +1 -0
  83. package/src/assets/icons/minus.svg +1 -0
  84. package/src/assets/icons/moon.svg +1 -0
  85. package/src/assets/icons/more-h.svg +1 -0
  86. package/src/assets/icons/mouse.svg +1 -0
  87. package/src/assets/icons/palette.svg +1 -0
  88. package/src/assets/icons/panel-bottom.svg +1 -0
  89. package/src/assets/icons/pause.svg +1 -0
  90. package/src/assets/icons/play.svg +1 -0
  91. package/src/assets/icons/plug.svg +1 -0
  92. package/src/assets/icons/plus.0c0c0d.svg +1 -0
  93. package/src/assets/icons/plus.svg +1 -0
  94. package/src/assets/icons/refresh.svg +1 -0
  95. package/src/assets/icons/ruler.svg +1 -0
  96. package/src/assets/icons/scroll-text.svg +1 -0
  97. package/src/assets/icons/search-menu.svg +1 -0
  98. package/src/assets/icons/search.svg +1 -0
  99. package/src/assets/icons/send.0c0c0d.svg +1 -0
  100. package/src/assets/icons/send.svg +1 -0
  101. package/src/assets/icons/settings.0c0c0d.svg +1 -0
  102. package/src/assets/icons/settings.svg +1 -0
  103. package/src/assets/icons/shield.svg +1 -0
  104. package/src/assets/icons/sidebar-left.svg +1 -0
  105. package/src/assets/icons/sidebar-right.svg +1 -0
  106. package/src/assets/icons/sliders.svg +1 -0
  107. package/src/assets/icons/sparkles.32f08c.svg +1 -0
  108. package/src/assets/icons/sparkles.svg +1 -0
  109. package/src/assets/icons/square.svg +1 -0
  110. package/src/assets/icons/star.svg +1 -0
  111. package/src/assets/icons/sun.svg +1 -0
  112. package/src/assets/icons/terminal.svg +1 -0
  113. package/src/assets/icons/trash.svg +1 -0
  114. package/src/assets/icons/trending-down.svg +1 -0
  115. package/src/assets/icons/trending-up.svg +1 -0
  116. package/src/assets/icons/type.svg +1 -0
  117. package/src/assets/icons/upload.svg +1 -0
  118. package/src/assets/icons/user-circle.svg +1 -0
  119. package/src/assets/icons/user.svg +1 -0
  120. package/src/assets/icons/users.svg +1 -0
  121. package/src/assets/icons/wrench.svg +1 -0
  122. package/src/assets/icons/x-circle.svg +1 -0
  123. package/src/assets/icons/x.svg +1 -0
  124. package/src/assets/icons/zap.0c0c0d.svg +1 -0
  125. package/src/assets/icons/zap.svg +1 -0
  126. package/src/avatar.ts +64 -0
  127. package/src/button.ts +88 -0
  128. package/src/card.ts +59 -0
  129. package/src/chat-composer.ts +156 -0
  130. package/src/dialog.ts +127 -0
  131. package/src/drawer.ts +131 -0
  132. package/src/editor-tabs.ts +184 -0
  133. package/src/file-tree.ts +214 -0
  134. package/src/forms.ts +270 -0
  135. package/src/icon.ts +117 -0
  136. package/src/icons.js +165 -0
  137. package/src/index.test.ts +213 -0
  138. package/src/index.ts +94 -0
  139. package/src/kbd.ts +72 -0
  140. package/src/menu.ts +119 -0
  141. package/src/nav-list.ts +157 -0
  142. package/src/overlay.test.ts +66 -0
  143. package/src/overlay.ts +205 -0
  144. package/src/page-header.ts +97 -0
  145. package/src/pagination.ts +163 -0
  146. package/src/second-batch.test.ts +205 -0
  147. package/src/stat-card.ts +92 -0
  148. package/src/status-bar.ts +108 -0
  149. package/src/styles/base.css +240 -0
  150. package/src/styles/components.css +1234 -0
  151. package/{dist → src/styles}/styles.css +2 -0
  152. package/src/styles/toast.css +57 -0
  153. package/src/styles/tokens.css +438 -0
  154. package/src/styles/vui.css +47 -0
  155. package/src/table.ts +191 -0
  156. package/src/tabs.ts +100 -0
  157. package/src/tag.ts +33 -0
  158. package/src/third-batch.test.ts +137 -0
  159. package/src/toast.test.ts +55 -0
  160. package/src/toast.ts +79 -0
  161. package/src/types.ts +25 -0
  162. package/src/utils.ts +221 -0
  163. package/src/workbench-titlebar.ts +137 -0
  164. package/dist/app-shell-dom.d.ts +0 -30
  165. package/dist/app-shell-dom.js +0 -913
  166. package/dist/app-shell.d.ts +0 -235
  167. package/dist/app-shell.js +0 -415
  168. package/dist/base.css +0 -51
  169. package/dist/calendar-picker.d.ts +0 -45
  170. package/dist/calendar-picker.js +0 -217
  171. package/dist/calendar.d.ts +0 -95
  172. package/dist/calendar.js +0 -194
  173. package/dist/complex-inputs.d.ts +0 -300
  174. package/dist/complex-inputs.js +0 -866
  175. package/dist/components.css +0 -4756
  176. package/dist/data-display.d.ts +0 -1094
  177. package/dist/data-display.js +0 -2341
  178. package/dist/dialog.d.ts +0 -26
  179. package/dist/dialog.js +0 -94
  180. package/dist/feedback.d.ts +0 -88
  181. package/dist/feedback.js +0 -281
  182. package/dist/forms.d.ts +0 -378
  183. package/dist/forms.js +0 -1019
  184. package/dist/icons-config.d.ts +0 -25
  185. package/dist/icons-config.js +0 -14
  186. package/dist/icons.d.ts +0 -41
  187. package/dist/icons.js +0 -185
  188. package/dist/index.d.ts +0 -31
  189. package/dist/index.js +0 -40
  190. package/dist/locale/en-US.d.ts +0 -31
  191. package/dist/locale/en-US.js +0 -20
  192. package/dist/locale/zh-CN.d.ts +0 -31
  193. package/dist/locale/zh-CN.js +0 -20
  194. package/dist/navigation.d.ts +0 -609
  195. package/dist/navigation.js +0 -1707
  196. package/dist/overlay.d.ts +0 -308
  197. package/dist/overlay.js +0 -974
  198. package/dist/page-state.d.ts +0 -23
  199. package/dist/page-state.js +0 -96
  200. package/dist/primitives.d.ts +0 -268
  201. package/dist/primitives.js +0 -764
  202. package/dist/theme-data.d.ts +0 -394
  203. package/dist/theme-data.js +0 -417
  204. package/dist/theme.d.ts +0 -97
  205. package/dist/theme.js +0 -301
  206. package/dist/tokens.css +0 -570
  207. package/dist/tree.d.ts +0 -144
  208. package/dist/tree.js +0 -475
  209. package/dist/virtual-list.d.ts +0 -190
  210. package/dist/virtual-list.js +0 -538
  211. package/dist/workbench.d.ts +0 -136
  212. package/dist/workbench.js +0 -156
package/src/icons.js ADDED
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Lightweight icon set inspired by Lucide / shadcn — square, 2px stroke,
3
+ * butt caps, miter joins. Use <i data-icon="name"></i> and call Icons.render().
4
+ */
5
+ (function (global) {
6
+ const ATTRS = 'xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"';
7
+
8
+ const PATHS = {
9
+ // navigation
10
+ 'globe': '<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 0 20"/><path d="M12 2a15.3 15.3 0 0 0 0 20"/>',
11
+ 'dashboard': '<rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/>',
12
+ 'mouse': '<rect x="6" y="2" width="12" height="20" rx="6"/><line x1="12" y1="6" x2="12" y2="11"/>',
13
+ 'search': '<circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>',
14
+ 'square': '<rect x="3" y="3" width="18" height="18"/>',
15
+ 'circle': '<circle cx="12" cy="12" r="9"/>',
16
+ 'list': '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
17
+ 'palette': '<rect x="3" y="3" width="8" height="8"/><rect x="13" y="3" width="8" height="8"/><rect x="3" y="13" width="8" height="8"/><rect x="13" y="13" width="8" height="8"/>',
18
+ 'type': '<polyline points="4 7 4 4 20 4 20 7"/><line x1="9" y1="20" x2="15" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/>',
19
+ 'ruler': '<rect x="3" y="9" width="18" height="6" transform="rotate(-45 12 12)"/><line x1="7.5" y1="12.5" x2="9" y2="14"/><line x1="11" y1="9" x2="12.5" y2="10.5"/><line x1="14.5" y1="5.5" x2="16" y2="7"/>',
20
+ 'sparkles': '<path d="M12 3l1.8 4.2L18 9l-4.2 1.8L12 15l-1.8-4.2L6 9l4.2-1.8L12 3z"/><path d="M19 14l1 2.2 2.2 1-2.2 1L19 20.4l-1-2.2-2.2-1 2.2-1L19 14z"/>',
21
+ // actions
22
+ 'copy': '<rect x="8" y="8" width="13" height="13"/><path d="M16 8V4H4v13h4"/>',
23
+ 'download': '<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><line x1="3" y1="21" x2="21" y2="21"/>',
24
+ 'refresh': '<polyline points="21 4 21 10 15 10"/><polyline points="3 20 3 14 9 14"/><path d="M20.5 9A9 9 0 0 0 5 5.5L3 7M3.5 15A9 9 0 0 0 19 18.5L21 17"/>',
25
+ 'external': '<polyline points="14 4 20 4 20 10"/><line x1="20" y1="4" x2="11" y2="13"/><path d="M20 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5"/>',
26
+ 'settings': '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/>',
27
+ 'sliders': '<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>',
28
+ 'plus': '<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>',
29
+ 'minus': '<line x1="5" y1="12" x2="19" y2="12"/>',
30
+ 'x': '<line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/>',
31
+ 'github': '<path d="M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2 2.8-.3 5.5-1.4 5.5-6a4.6 4.6 0 0 0-1.3-3.2 4.2 4.2 0 0 0-.1-3.2s-1.1-.3-3.5 1.3a12 12 0 0 0-6.2 0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2 0 0 0-.1 3.2A4.6 4.6 0 0 0 4 9.5c0 4.6 2.7 5.7 5.5 6-.6.6-.6 1.2-.5 2V21"/>',
32
+ 'message-circle':'<path d="M21 12a9 9 0 0 1-13.5 7.8L3 21l1.2-4.5A9 9 0 1 1 21 12z"/>',
33
+ // SOLO chat title-bar glyphs (Figma 2510_18334) — round bubble + plus,
34
+ // 4-tooth gear with center circle, and person-in-circle account avatar.
35
+ 'message-plus':'<path d="M21 12a9 9 0 0 1-13.5 7.8L3 21l1.2-4.5A9 9 0 1 1 21 12z"/><line x1="12" y1="9" x2="12" y2="15"/><line x1="9" y1="12" x2="15" y2="12"/>',
36
+ 'gear': '<path d="M9.3 5.7 6.375 5.025 5.025 6.375 5.7 9.3 3 11.1 3 12.9 5.7 14.7 5.025 17.625 6.375 18.975 9.3 18.3 11.1 21 12.9 21 14.7 18.3 17.625 18.975 18.975 17.625 18.3 14.7 21 12.9 21 11.1 18.3 9.3 18.975 6.375 17.625 5.025 14.7 5.7 12.9 3 11.1 3 9.3 5.7Z"/><circle cx="12" cy="12" r="3"/>',
37
+ 'user-circle': '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="10" r="2.5"/><path d="M7 17.5a5 5 0 0 1 10 0"/>',
38
+ 'shield': '<path d="M12 3 4 6v6c0 5 3.5 8 8 9 4.5-1 8-4 8-9V6z"/>',
39
+ 'check': '<polyline points="4 12 10 18 20 6"/>',
40
+ 'arrow-right': '<line x1="4" y1="12" x2="20" y2="12"/><polyline points="14 6 20 12 14 18"/>',
41
+ 'arrow-up-right': '<line x1="6" y1="18" x2="18" y2="6"/><polyline points="9 6 18 6 18 15"/>',
42
+ 'chevron-right':'<polyline points="9 6 15 12 9 18"/>',
43
+ 'chevron-down':'<polyline points="6 9 12 15 18 9"/>',
44
+ // status — strict outline, 24x24, 2px stroke (no filled bowls)
45
+ 'check-circle':'<circle cx="12" cy="12" r="9"/><polyline points="8 12 11 15 16 9"/>',
46
+ 'alert-circle':'<circle cx="12" cy="12" r="9"/><line x1="12" y1="8" x2="12" y2="13"/><line x1="12" y1="16" x2="12" y2="16.01"/>',
47
+ 'alert-triangle':'<path d="M12 3 22 20 2 20 Z"/><line x1="12" y1="10" x2="12" y2="15"/><line x1="12" y1="18" x2="12" y2="18.01"/>',
48
+ 'info-circle': '<circle cx="12" cy="12" r="9"/><line x1="12" y1="11" x2="12" y2="16"/><line x1="12" y1="8" x2="12" y2="8.01"/>',
49
+ 'x-circle': '<circle cx="12" cy="12" r="9"/><line x1="9" y1="9" x2="15" y2="15"/><line x1="15" y1="9" x2="9" y2="15"/>',
50
+ // legacy aliases — kept outline, square frame
51
+ 'alert': '<path d="M12 3 22 20 2 20 Z"/><line x1="12" y1="10" x2="12" y2="15"/><line x1="12" y1="18" x2="12" y2="18.01"/>',
52
+ 'info': '<circle cx="12" cy="12" r="9"/><line x1="12" y1="11" x2="12" y2="16"/><line x1="12" y1="8" x2="12" y2="8.01"/>',
53
+ // misc
54
+ 'mail': '<rect x="3" y="5" width="18" height="14"/><polyline points="3 6 12 13 21 6"/>',
55
+ 'user': '<path d="M4 21v-1a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v1"/><circle cx="12" cy="8" r="4"/>',
56
+ 'users': '<path d="M2 21v-1a5 5 0 0 1 5-5h3a5 5 0 0 1 5 5v1"/><circle cx="8.5" cy="8" r="3.5"/><path d="M22 21v-1a5 5 0 0 0-4-4.9"/><path d="M16 3.1A4 4 0 0 1 16 11"/>',
57
+ 'box': '<polyline points="3 7 12 2 21 7 21 17 12 22 3 17 3 7"/><line x1="3" y1="7" x2="12" y2="12"/><line x1="21" y1="7" x2="12" y2="12"/><line x1="12" y1="22" x2="12" y2="12"/>',
58
+ 'zap': '<polygon points="13 2 4 14 12 14 11 22 20 10 12 10 13 2"/>',
59
+ 'moon': '<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>',
60
+ 'sun': '<circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="4.6" y1="4.6" x2="6.7" y2="6.7"/><line x1="17.3" y1="17.3" x2="19.4" y2="19.4"/><line x1="4.6" y1="19.4" x2="6.7" y2="17.3"/><line x1="17.3" y1="6.7" x2="19.4" y2="4.6"/>',
61
+ 'cmd': '<path d="M9 6h6v12H9z"/><rect x="3" y="3" width="6" height="6"/><rect x="15" y="3" width="6" height="6"/><rect x="3" y="15" width="6" height="6"/><rect x="15" y="15" width="6" height="6"/>',
62
+ 'key': '<circle cx="7.5" cy="14.5" r="3.5"/><line x1="10" y1="12" x2="22" y2="12"/><line x1="22" y1="12" x2="22" y2="16"/><line x1="18" y1="12" x2="18" y2="15"/>',
63
+ 'bell': '<path d="M6 8a6 6 0 0 1 12 0c0 7 3 8 3 8H3s3-1 3-8z"/><path d="M10 21a2 2 0 0 0 4 0"/>',
64
+ 'arrow-up': '<line x1="12" y1="20" x2="12" y2="4"/><polyline points="6 10 12 4 18 10"/>',
65
+ 'chevron-up': '<polyline points="6 15 12 9 18 15"/>',
66
+ 'arrow-left': '<line x1="20" y1="12" x2="4" y2="12"/><polyline points="10 6 4 12 10 18"/>',
67
+ 'mic': '<rect x="9" y="3" width="6" height="12" rx="3"/><path d="M5 11a7 7 0 0 0 14 0"/><line x1="12" y1="18" x2="12" y2="22"/>',
68
+ 'at': '<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/>',
69
+ 'hash': '<line x1="4" y1="9" x2="20" y2="9"/><line x1="4" y1="15" x2="20" y2="15"/><line x1="10" y1="3" x2="8" y2="21"/><line x1="16" y1="3" x2="14" y2="21"/>',
70
+ 'sidebar-left': '<rect x="3" y="3" width="18" height="18"/><line x1="9" y1="3" x2="9" y2="21"/>',
71
+ 'sidebar-right': '<rect x="3" y="3" width="18" height="18"/><line x1="15" y1="3" x2="15" y2="21"/>',
72
+ 'panel-bottom': '<rect x="3" y="3" width="18" height="18"/><line x1="3" y1="15" x2="21" y2="15"/>',
73
+ /* ===== Activity-bar icon set =====================================
74
+ * Lucide / shadcn-ui (MIT) — squared outline, 2px stroke. Every glyph
75
+ * draws into the same standard 24-grid bbox (≈3..21) so they read at
76
+ * identical optical size when sized via `data-size` alone, just like
77
+ * every other glyph in this file. See skill.md §13.4. */
78
+ 'git': '<circle cx="6" cy="5" r="3"/><circle cx="6" cy="19" r="3"/><circle cx="18" cy="5" r="3"/><line x1="6" y1="8" x2="6" y2="16"/><path d="M18 8v3a4 4 0 0 1-4 4h-4"/>',
79
+ 'bug': '<rect x="8" y="6" width="8" height="14" rx="4"/><line x1="12" y1="11" x2="12" y2="20"/><line x1="3" y1="9" x2="8" y2="9"/><line x1="3" y1="14" x2="8" y2="14"/><line x1="3" y1="19" x2="8" y2="19"/><line x1="16" y1="9" x2="21" y2="9"/><line x1="16" y1="14" x2="21" y2="14"/><line x1="16" y1="19" x2="21" y2="19"/><line x1="9" y1="6" x2="9" y2="3"/><line x1="15" y1="6" x2="15" y2="3"/>',
80
+ 'search-menu': '<circle cx="11" cy="11" r="6"/><line x1="20" y1="20" x2="16" y2="16"/><line x1="3" y1="20" x2="13" y2="20"/>',
81
+ 'extensions': '<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><path d="M17.5 14v3.5H21a2 2 0 0 1 0 4h-3.5V21a2 2 0 0 1-4 0v-3.5H14a2 2 0 0 1 0-4h3.5z"/>',
82
+ // settings sidebar — squared, hard-edged outline (Lucide-ish), 2px stroke
83
+ 'wrench': '<path d="M14.7 6.3a4 4 0 0 0 5 5L21 12.5l-7.5 7.5a3 3 0 0 1-4.2-4.2L16.7 8 14.7 6.3z"/><path d="M14.7 6.3 12 9l-3-3 2.7-2.7a4 4 0 0 1 3 3z"/>',
84
+ 'message-square': '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
85
+ 'file-text': '<path d="M14 3H6v18h12V8z"/><polyline points="14 3 14 8 18 8"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/>',
86
+ 'scroll-text': '<path d="M5 4h11a3 3 0 0 1 3 3v10H8v3a1 1 0 0 1-1 1 3 3 0 0 1-3-3V7a3 3 0 0 1 1-3z"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="9" y1="13" x2="15" y2="13"/>',
87
+ 'atom': '<circle cx="12" cy="12" r="2"/><ellipse cx="12" cy="12" rx="10" ry="4"/><ellipse cx="12" cy="12" rx="10" ry="4" transform="rotate(60 12 12)"/><ellipse cx="12" cy="12" rx="10" ry="4" transform="rotate(120 12 12)"/>',
88
+ 'arrow-right-to-line': '<line x1="20" y1="4" x2="20" y2="20"/><line x1="3" y1="12" x2="17" y2="12"/><polyline points="11 6 17 12 11 18"/>',
89
+ 'info-square': '<rect x="3" y="3" width="18" height="18"/><line x1="12" y1="11" x2="12" y2="16"/><line x1="12" y1="8" x2="12" y2="8.01"/>',
90
+ /* Window-control glyphs for SOLO top bar (Figma 2510_20754):
91
+ * .arrow-minimize → diagonal-in arrows (collapse to corners)
92
+ * .icon-expand → diagonal-out arrows (expand to corners) */
93
+ 'arrow-minimize': '<polyline points="20 4 14 10 20 10"/><line x1="14" y1="10" x2="14" y2="4"/><polyline points="4 20 10 14 4 14"/><line x1="10" y1="14" x2="10" y2="20"/>',
94
+ 'arrow-expand': '<polyline points="14 4 20 4 20 10"/><line x1="14" y1="10" x2="20" y2="4"/><polyline points="10 20 4 20 4 14"/><line x1="10" y1="14" x2="4" y2="20"/>',
95
+ 'arrow-down': '<line x1="12" y1="4" x2="12" y2="20"/><polyline points="6 14 12 20 18 14"/>',
96
+ 'logo': '<rect x="3" y="3" width="18" height="18"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/>',
97
+ // additional UI
98
+ 'more-h': '<circle cx="5" cy="12" r="1.5" fill="currentColor"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/><circle cx="19" cy="12" r="1.5" fill="currentColor"/>',
99
+ 'edit': '<path d="M4 20h4l10-10-4-4L4 16v4z"/><path d="M14 6l4 4"/>',
100
+ 'trash': '<polyline points="4 6 20 6"/><path d="M6 6v14h12V6"/><path d="M9 6V4h6v2"/><line x1="10" y1="10" x2="10" y2="17"/><line x1="14" y1="10" x2="14" y2="17"/>',
101
+ 'file': '<path d="M14 3H6v18h12V7z"/><polyline points="14 3 14 7 18 7"/>',
102
+ // Stacked documents — Lucide "files" (3..21).
103
+ 'files': '<path d="M21 8v13H8V3h8z"/><polyline points="16 3 16 8 21 8"/><path d="M8 7H3v14h13v-3"/>',
104
+ // Pure 2×2 grid — Lucide "layout-grid" (3..21).
105
+ 'grid-2x2': '<rect x="3" y="3" width="8" height="8"/><rect x="13" y="3" width="8" height="8"/><rect x="3" y="13" width="8" height="8"/><rect x="13" y="13" width="8" height="8"/>',
106
+ 'folder': '<path d="M3 6h6l2 3h10v10H3z"/>',
107
+ 'layers': '<polygon points="12 3 22 8 12 13 2 8 12 3"/><polyline points="2 13 12 18 22 13"/>',
108
+ 'layout': '<rect x="3" y="3" width="18" height="18"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/>',
109
+ 'terminal': '<polyline points="4 7 9 12 4 17"/><line x1="12" y1="17" x2="20" y2="17"/>',
110
+ 'image': '<rect x="3" y="3" width="18" height="18"/><circle cx="9" cy="9" r="1.5" fill="currentColor"/><polyline points="3 18 9 12 13 16 17 12 21 16"/>',
111
+ 'play': '<polygon points="6 4 20 12 6 20 6 4"/>',
112
+ 'pause': '<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>',
113
+ 'help': '<rect x="3" y="3" width="18" height="18"/><path d="M9 9a3 3 0 0 1 6 0c0 2-3 2-3 4"/><line x1="12" y1="17" x2="12" y2="17.01"/>',
114
+ 'lock': '<rect x="4" y="11" width="16" height="10"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/>',
115
+ // Lucide "eye" (2..22 horizontal, naturally short — eye shape).
116
+ 'eye': '<path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>',
117
+ 'star': '<polygon points="12 3 15 9 22 10 17 14 18 21 12 18 6 21 7 14 2 10 9 9 12 3"/>',
118
+ 'heart': '<path d="M12 21s-7-5-7-11a4 4 0 0 1 7-2 4 4 0 0 1 7 2c0 6-7 11-7 11z"/>',
119
+ 'home': '<polygon points="3 11 12 3 21 11 21 21 14 21 14 14 10 14 10 21 3 21 3 11"/>',
120
+ 'calendar': '<rect x="3" y="5" width="18" height="16"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="8" y1="3" x2="8" y2="7"/><line x1="16" y1="3" x2="16" y2="7"/>',
121
+ 'clock': '<circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 16 14"/>',
122
+ 'filter': '<polygon points="3 4 21 4 14 12 14 20 10 18 10 12 3 4"/>',
123
+ 'send': '<polygon points="3 12 21 4 17 21 12 13 3 12"/>',
124
+ 'link': '<path d="M10 14a4 4 0 0 1 0-6l3-3a4 4 0 0 1 6 6l-1.5 1.5"/><path d="M14 10a4 4 0 0 1 0 6l-3 3a4 4 0 0 1-6-6l1.5-1.5"/>',
125
+ 'upload': '<path d="M12 21V9"/><polyline points="7 14 12 9 17 14"/><line x1="3" y1="3" x2="21" y2="3"/>',
126
+ 'log-out': '<path d="M14 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9"/><polyline points="16 16 21 12 16 8"/><line x1="9" y1="12" x2="21" y2="12"/>',
127
+ 'menu': '<line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/>',
128
+ // data / charts
129
+ 'dollar': '<line x1="12" y1="2" x2="12" y2="22"/><path d="M17 6H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>',
130
+ 'bar': '<line x1="3" y1="21" x2="21" y2="21"/><rect x="5" y="11" width="3" height="8"/><rect x="10.5" y="6" width="3" height="13"/><rect x="16" y="14" width="3" height="5"/>',
131
+ 'trending-up': '<polyline points="3 17 9 11 13 15 21 7"/><polyline points="15 7 21 7 21 13"/>',
132
+ 'trending-down': '<polyline points="3 7 9 13 13 9 21 17"/><polyline points="15 17 21 17 21 11"/>',
133
+ 'columns': '<rect x="3" y="3" width="18" height="18"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/>',
134
+ // settings — framework rules
135
+ 'plug': '<path d="M9 2v6"/><path d="M15 2v6"/><path d="M7 8h10v4a5 5 0 0 1-10 0V8z"/><path d="M12 17v5"/>',
136
+ 'cpu': '<rect x="6" y="6" width="12" height="12"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="2" x2="9" y2="6"/><line x1="15" y1="2" x2="15" y2="6"/><line x1="9" y1="18" x2="9" y2="22"/><line x1="15" y1="18" x2="15" y2="22"/><line x1="2" y1="9" x2="6" y2="9"/><line x1="2" y1="15" x2="6" y2="15"/><line x1="18" y1="9" x2="22" y2="9"/><line x1="18" y1="15" x2="22" y2="15"/>',
137
+ 'code': '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
138
+ // brand — filled Apple glyph for download CTA
139
+ 'apple': '<path fill="currentColor" stroke="none" d="M17.05 12.04c-.03-3.04 2.49-4.5 2.6-4.57-1.42-2.07-3.62-2.36-4.4-2.39-1.87-.19-3.65 1.1-4.6 1.1-.96 0-2.42-1.08-3.98-1.05-2.05.03-3.94 1.19-4.99 3.02-2.13 3.69-.54 9.13 1.53 12.12 1.01 1.46 2.21 3.1 3.78 3.04 1.52-.06 2.09-.98 3.93-.98 1.83 0 2.36.98 3.97.95 1.64-.03 2.68-1.49 3.68-2.96 1.16-1.7 1.64-3.35 1.66-3.43-.04-.02-3.18-1.22-3.21-4.85zM14.06 4.34c.83-1.01 1.39-2.41 1.24-3.81-1.2.05-2.65.8-3.51 1.8-.77.89-1.45 2.31-1.27 3.68 1.34.1 2.71-.68 3.54-1.67z"/>',
140
+ };
141
+
142
+ function svg(name, size = 16, viewBox = '0 0 24 24') {
143
+ const path = PATHS[name];
144
+ if (!path) return '';
145
+ const attrs = ATTRS
146
+ .replace('width="16"', `width="${size}"`)
147
+ .replace('height="16"', `height="${size}"`)
148
+ .replace('viewBox="0 0 24 24"', `viewBox="${viewBox}"`);
149
+ return `<svg ${attrs}>${path}</svg>`;
150
+ }
151
+
152
+ function render(root = document) {
153
+ root.querySelectorAll('[data-icon]').forEach(el => {
154
+ const name = el.getAttribute('data-icon');
155
+ const size = +el.getAttribute('data-size') || 16;
156
+ const vb = el.getAttribute('data-viewbox') || '0 0 24 24';
157
+ el.innerHTML = svg(name, size, vb);
158
+ el.classList.add('icon');
159
+ });
160
+ }
161
+
162
+ global.Icons = { svg, render, PATHS };
163
+ if (document.readyState !== 'loading') render();
164
+ else document.addEventListener('DOMContentLoaded', () => render());
165
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -0,0 +1,213 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest'
2
+ import { state } from '@vobs/reactivity'
3
+ import {
4
+ createComponent,
5
+ createDOMRenderer,
6
+ createElement,
7
+ createText,
8
+ createVobs,
9
+ setRenderer
10
+ } from '@vobs/vobs'
11
+ import { Alert } from './alert'
12
+ import { Button } from './button'
13
+ import { Card } from './card'
14
+ import { Dialog } from './dialog'
15
+ import { Icon } from './icon'
16
+ import { Input } from './forms'
17
+ import { Tabs } from './tabs'
18
+ import { Tag } from './tag'
19
+
20
+ describe('@vobs/ui', () => {
21
+ beforeEach(() => {
22
+ setRenderer(createDOMRenderer())
23
+ })
24
+
25
+ it('Button 合并外部 class,并响应动态 disabled/loading', () => {
26
+ const disabled = state(false)
27
+ const loading = state(false)
28
+ let clicks = 0
29
+ const props = {
30
+ get disabled() { return disabled.value },
31
+ get loading() { return loading.value },
32
+ class: 'consumer-button',
33
+ className: 'another-class',
34
+ children: 'Save',
35
+ onClick: () => { clicks++ }
36
+ }
37
+ const container = document.createElement('main')
38
+ const app = createVobs({ render: () => createComponent(Button, props) })
39
+ app.mount(container)
40
+
41
+ const button = container.querySelector('button')!
42
+ expect(button.className).toContain('vui-btn')
43
+ expect(button.className).toContain('consumer-button')
44
+ expect(button.className).toContain('another-class')
45
+ expect(button.textContent).toBe('Save')
46
+
47
+ button.dispatchEvent(new MouseEvent('click'))
48
+ expect(clicks).toBe(1)
49
+ disabled.value = true
50
+ app.update()
51
+ expect(button.disabled).toBe(true)
52
+ button.dispatchEvent(new MouseEvent('click'))
53
+ expect(clicks).toBe(1)
54
+
55
+ loading.value = true
56
+ app.update()
57
+ expect(button.getAttribute('aria-busy')).toBe('true')
58
+ app.destroy()
59
+ })
60
+
61
+ it('Input 使用原生 input,并把受控 value 和事件交给用户 Signal', () => {
62
+ const value = state('before')
63
+ let inputEvent = 0
64
+ const props = {
65
+ get value() { return value.value },
66
+ class: 'consumer-input',
67
+ onInput: (event: InputEvent) => {
68
+ inputEvent++
69
+ value.value = (event.target as HTMLInputElement).value
70
+ }
71
+ }
72
+ const container = document.createElement('main')
73
+ const app = createVobs({ render: () => createComponent(Input, props) })
74
+ app.mount(container)
75
+
76
+ const root = container.querySelector('.vui-input')!
77
+ const input = root.querySelector('input')!
78
+ expect(root.className).toContain('consumer-input')
79
+ expect(input.value).toBe('before')
80
+
81
+ input.value = 'after'
82
+ input.dispatchEvent(new InputEvent('input', { bubbles: true }))
83
+ app.update()
84
+ expect(inputEvent).toBe(1)
85
+ expect(value.value).toBe('after')
86
+ expect(input.value).toBe('after')
87
+ app.destroy()
88
+ })
89
+
90
+ it('组件接受第三方节点作为 icon/children,不创建隐式图标依赖', () => {
91
+ const externalIcon = createElement('svg')
92
+ const externalLabel = createText('External')
93
+ const container = document.createElement('main')
94
+ const app = createVobs({
95
+ render: () => createComponent(Button, {
96
+ variant: 'secondary',
97
+ icon: externalIcon,
98
+ children: externalLabel
99
+ })
100
+ })
101
+ app.mount(container)
102
+
103
+ const button = container.querySelector('button')!
104
+ expect(button.querySelector('svg')).toBe(externalIcon)
105
+ expect(button.textContent).toBe('External')
106
+ app.destroy()
107
+ })
108
+
109
+ it('结构组件使用新版 vui- class contract', () => {
110
+ const container = document.createElement('main')
111
+ const app = createVobs({
112
+ render: () => {
113
+ const root = createElement('div')
114
+ root.append(
115
+ createComponent(Card, { title: 'Title', description: 'Description', class: 'consumer-card' }) as Node,
116
+ createComponent(Tag, { tone: 'success', children: 'Ready' }) as Node,
117
+ createComponent(Alert, { tone: 'info', title: 'Info', description: 'Details' }) as Node
118
+ )
119
+ return root
120
+ }
121
+ })
122
+ app.mount(container)
123
+
124
+ expect(container.querySelector('.vui-card')).toBeTruthy()
125
+ expect(container.querySelector('.vui-card')?.classList.contains('consumer-card')).toBe(true)
126
+ expect(container.querySelector('.vui-card__title')?.classList.contains('consumer-card')).toBe(false)
127
+ expect(container.querySelector('.vui-card__title')?.textContent).toBe('Title')
128
+ expect(container.querySelector('.vui-tag--success')?.textContent).toBe('Ready')
129
+ expect(container.querySelector('.vui-alert--info')).toBeTruthy()
130
+ expect(container.querySelector('[class*="ds-"]')).toBeNull()
131
+ app.destroy()
132
+ })
133
+
134
+ it('Tabs 在非受控模式下更新 active tab,并通知用户', () => {
135
+ const changes: string[] = []
136
+ const container = document.createElement('main')
137
+ const app = createVobs({
138
+ render: () => createComponent(Tabs, {
139
+ items: [
140
+ { id: 'overview', label: 'Overview', content: 'Overview content' },
141
+ { id: 'activity', label: 'Activity', content: 'Activity content' }
142
+ ],
143
+ onChange: id => changes.push(id)
144
+ })
145
+ })
146
+ app.mount(container)
147
+
148
+ const tabs = container.querySelectorAll('[role="tab"]')
149
+ expect(tabs[0]?.getAttribute('aria-selected')).toBe('true')
150
+ expect(container.querySelector('.vui-tabs__panel')?.textContent).toBe('Overview content')
151
+ ;(tabs[1] as HTMLElement).click()
152
+ app.update()
153
+ expect(changes).toEqual(['activity'])
154
+ expect(container.querySelector('[data-tab-id="activity"]')?.getAttribute('aria-selected')).toBe('true')
155
+ expect(container.querySelector('.vui-tabs__panel')?.textContent).toBe('Activity content')
156
+ app.destroy()
157
+ })
158
+
159
+ it('Dialog 支持 open、Escape 和 backdrop close reason', () => {
160
+ const reasons: string[] = []
161
+ const open = state(true)
162
+ const container = document.createElement('main')
163
+ const app = createVobs({
164
+ render: () => createComponent(Dialog, {
165
+ get open() { return open.value },
166
+ title: 'Confirm',
167
+ onClose: reason => reasons.push(reason),
168
+ children: 'Dialog body'
169
+ })
170
+ })
171
+ app.mount(container)
172
+
173
+ const root = container.firstElementChild as HTMLElement
174
+ expect(root.hidden).toBe(false)
175
+ expect(container.querySelector('[role="dialog"]')?.textContent).toContain('Dialog body')
176
+ root.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
177
+ expect(reasons).toEqual(['escape'])
178
+ root.dispatchEvent(new MouseEvent('click', { bubbles: true }))
179
+ expect(reasons).toEqual(['escape', 'backdrop'])
180
+ open.value = false
181
+ app.update()
182
+ expect(root.hidden).toBe(true)
183
+ app.destroy()
184
+ })
185
+
186
+ it('内置 Icon 是可选便利实现,并输出可命名 SVG', () => {
187
+ const container = document.createElement('main')
188
+ const app = createVobs({
189
+ render: () => createComponent(Icon, { name: 'search', size: 20, title: 'Search' })
190
+ })
191
+ app.mount(container)
192
+
193
+ const icon = container.querySelector('.vui-icon') as HTMLElement
194
+ expect(icon.dataset.iconName).toBe('search')
195
+ expect(icon.querySelector('svg')).toBeTruthy()
196
+ expect(icon.style.width).toBe('20px')
197
+ expect(icon.getAttribute('aria-label')).toBe('Search')
198
+ expect(icon.getAttribute('aria-hidden')).toBeNull()
199
+ app.destroy()
200
+ })
201
+
202
+ it('Icon 未传 size 时不写入内联尺寸', () => {
203
+ const container = document.createElement('main')
204
+ const app = createVobs({
205
+ render: () => createComponent(Icon, { name: 'search' })
206
+ })
207
+ app.mount(container)
208
+
209
+ const icon = container.querySelector('.vui-icon') as HTMLElement
210
+ expect(icon.hasAttribute('style')).toBe(false)
211
+ app.destroy()
212
+ })
213
+ })
package/src/index.ts ADDED
@@ -0,0 +1,94 @@
1
+ export { Button, ButtonGroup } from './button'
2
+ export type { ButtonProps, ButtonVariant, ButtonGroupProps, ControlSize } from './button'
3
+
4
+ export { Input, Textarea, Select, Checkbox, Radio, Switch } from './forms'
5
+ export type {
6
+ ChoiceProps,
7
+ InputProps,
8
+ InputType,
9
+ SelectProps,
10
+ SwitchProps,
11
+ TextareaProps
12
+ } from './forms'
13
+
14
+ export { Card, CardHeader } from './card'
15
+ export type { CardProps, CardHeaderProps } from './card'
16
+
17
+ export { Tag } from './tag'
18
+ export type { TagProps, TagTone } from './tag'
19
+
20
+ export { Alert } from './alert'
21
+ export type { AlertProps, AlertTone } from './alert'
22
+
23
+ export { Tabs } from './tabs'
24
+ export type { TabItem, TabsProps } from './tabs'
25
+
26
+ export { Dialog } from './dialog'
27
+ export type { DialogCloseReason, DialogProps } from './dialog'
28
+
29
+ export { Menu, MenuDivider } from './menu'
30
+ export type { MenuItem, MenuProps } from './menu'
31
+
32
+ export { NavList } from './nav-list'
33
+ export type { NavGroup, NavItem, NavListProps } from './nav-list'
34
+
35
+ export { Pagination } from './pagination'
36
+ export type { PaginationProps } from './pagination'
37
+
38
+ export { Avatar } from './avatar'
39
+ export type { AvatarProps, AvatarShape, AvatarSize } from './avatar'
40
+
41
+ export { PageHeader, PageHeaderAction } from './page-header'
42
+ export type { PageHeaderActionProps, PageHeaderProps } from './page-header'
43
+
44
+ export { Table, TablePanel } from './table'
45
+ export type { TableAlign, TableColumn, TablePanelProps, TableProps } from './table'
46
+
47
+ export { StatCard, StatCardGrid } from './stat-card'
48
+ export type { StatCardGridProps, StatCardProps, StatDeltaDirection } from './stat-card'
49
+
50
+ export { Kbd, KbdCombo, KbdRow } from './kbd'
51
+ export type { KbdComboProps, KbdProps, KbdRowProps } from './kbd'
52
+
53
+ export { ActivityRail } from './activity-rail'
54
+ export type { ActivityRailItem, ActivityRailProps } from './activity-rail'
55
+
56
+ export { FileTree } from './file-tree'
57
+ export type { FileTreeItem, FileTreeItemKind, FileTreeProps } from './file-tree'
58
+
59
+ export { EditorTabs } from './editor-tabs'
60
+ export type { EditorTabItem, EditorTabsProps } from './editor-tabs'
61
+
62
+ export { WorkbenchTitlebar } from './workbench-titlebar'
63
+ export type { WorkbenchTitlebarProps } from './workbench-titlebar'
64
+
65
+ export { StatusBar } from './status-bar'
66
+ export type { StatusBarDot, StatusBarItem, StatusBarProps } from './status-bar'
67
+
68
+ export { ChatComposer } from './chat-composer'
69
+ export type { ChatComposerProps } from './chat-composer'
70
+
71
+ export { Drawer } from './drawer'
72
+ export type { DrawerCloseReason, DrawerProps } from './drawer'
73
+
74
+ export { ToastHost } from './toast'
75
+ export type { ToastHostProps, ToastPosition } from './toast'
76
+
77
+ export {
78
+ createDOMPortalAdapter,
79
+ createFocusTrap,
80
+ createOverlayManager,
81
+ createPortal
82
+ } from './overlay'
83
+ export type {
84
+ FocusTrapHandle,
85
+ FocusTrapOptions,
86
+ OverlayEntry,
87
+ OverlayManager,
88
+ VuiPortalAdapter
89
+ } from './overlay'
90
+
91
+ export { Icon, createIcon, registerIcon, resolveIcon, VUI_ICON_PATHS } from './icon'
92
+ export type { IconDefinition, IconProps } from './icon'
93
+
94
+ export type { VuiAttributeValue, VuiChildren, VuiCommonProps, VuiEventHandler } from './types'
package/src/kbd.ts ADDED
@@ -0,0 +1,72 @@
1
+ import {
2
+ createElement,
3
+ createFragment,
4
+ insertBefore,
5
+ insertDynamic,
6
+ setAttribute,
7
+ type VobsNode
8
+ } from '@vobs/vobs'
9
+ import {
10
+ bindClassList,
11
+ bindCommonAttributes,
12
+ bindUserStyle,
13
+ hasProp,
14
+ mountSlot,
15
+ readProp,
16
+ resolveSlot
17
+ } from './utils'
18
+ import type { VuiChildren, VuiCommonProps } from './types'
19
+
20
+ export interface KbdProps extends VuiCommonProps {}
21
+
22
+ export function Kbd(props: KbdProps = {}): VobsNode {
23
+ const root = createElement('kbd')
24
+ bindClassList(root, props, () => ['vui-kbd'])
25
+ bindCommonAttributes(root, props)
26
+ bindUserStyle(root, props)
27
+ if (hasProp(props, 'children')) mountSlot(root, props, 'children')
28
+ return root
29
+ }
30
+
31
+ export interface KbdComboProps extends VuiCommonProps {
32
+ readonly keys?: readonly VuiChildren[]
33
+ readonly separator?: VuiChildren
34
+ }
35
+
36
+ export function KbdCombo(props: KbdComboProps = {}): VobsNode {
37
+ const root = createElement('span')
38
+ bindClassList(root, props, () => ['vui-kbd__combo'])
39
+ bindCommonAttributes(root, props, ['keys', 'separator'])
40
+ bindUserStyle(root, props)
41
+ if (hasProp(props, 'keys')) insertDynamic(root, null, () => createComboKeys(props))
42
+ if (hasProp(props, 'children')) mountSlot(root, props, 'children')
43
+ return root
44
+ }
45
+
46
+ export interface KbdRowProps extends VuiCommonProps {}
47
+
48
+ export function KbdRow(props: KbdRowProps = {}): VobsNode {
49
+ const root = createElement('div')
50
+ bindClassList(root, props, () => ['vui-kbd__row'])
51
+ bindCommonAttributes(root, props)
52
+ bindUserStyle(root, props)
53
+ if (hasProp(props, 'children')) mountSlot(root, props, 'children')
54
+ return root
55
+ }
56
+
57
+ function createComboKeys(props: KbdComboProps): VobsNode {
58
+ const keys = readProp<readonly VuiChildren[]>(props, 'keys', [])
59
+ const separator = readProp<VuiChildren>(props, 'separator', '+')
60
+ return createFragment((parent, anchor) => {
61
+ for (let index = 0; index < keys.length; index++) {
62
+ if (index > 0) {
63
+ const plus = createElement('span')
64
+ setAttribute(plus, 'class', 'vui-kbd__plus')
65
+ const separatorNode = resolveSlot(separator)
66
+ if (separatorNode) insertBefore(plus, separatorNode, null)
67
+ insertBefore(parent, plus, anchor)
68
+ }
69
+ insertBefore(parent, Kbd({ children: keys[index] }), anchor)
70
+ }
71
+ })
72
+ }