@greypan/web-ui 1.4.2 → 2.0.1

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 (346) hide show
  1. package/README.CN.md +674 -232
  2. package/README.md +683 -237
  3. package/dist/_virtual/{_@oxc-project_runtime@0.138.0 → _@oxc-project_runtime@0.140.0}/helpers/esm/decorate.js +1 -1
  4. package/dist/assets/glass.js +6 -0
  5. package/dist/assets/glass.js.map +1 -0
  6. package/dist/assets/menu-portal.js +6 -0
  7. package/dist/assets/menu-portal.js.map +1 -0
  8. package/dist/assets/overlay-motion.js +6 -0
  9. package/dist/assets/overlay-motion.js.map +1 -0
  10. package/dist/assets/toast-containers.js +6 -0
  11. package/dist/assets/toast-containers.js.map +1 -0
  12. package/dist/components/avatar/index.d.ts +30 -0
  13. package/dist/components/avatar/index.js +112 -0
  14. package/dist/components/avatar/index.js.map +1 -0
  15. package/dist/components/avatar/style.js +6 -0
  16. package/dist/components/avatar/style.js.map +1 -0
  17. package/dist/components/back-top/index.d.ts +8 -7
  18. package/dist/components/back-top/index.js +37 -32
  19. package/dist/components/back-top/index.js.map +1 -1
  20. package/dist/components/back-top/style.js +1 -1
  21. package/dist/components/back-top/style.js.map +1 -1
  22. package/dist/components/badge/index.d.ts +27 -0
  23. package/dist/components/badge/index.js +121 -0
  24. package/dist/components/badge/index.js.map +1 -0
  25. package/dist/components/badge/style.js +6 -0
  26. package/dist/components/badge/style.js.map +1 -0
  27. package/dist/components/button/index.d.ts +16 -3
  28. package/dist/components/button/index.js +56 -17
  29. package/dist/components/button/index.js.map +1 -1
  30. package/dist/components/button/style.js +1 -1
  31. package/dist/components/button/style.js.map +1 -1
  32. package/dist/components/button-group/index.d.ts +16 -0
  33. package/dist/components/button-group/index.js +49 -0
  34. package/dist/components/button-group/index.js.map +1 -0
  35. package/dist/components/button-group/style.js +6 -0
  36. package/dist/components/button-group/style.js.map +1 -0
  37. package/dist/components/checkbox/index.d.ts +39 -0
  38. package/dist/components/checkbox/index.js +111 -0
  39. package/dist/components/checkbox/index.js.map +1 -0
  40. package/dist/components/checkbox/style.js +6 -0
  41. package/dist/components/checkbox/style.js.map +1 -0
  42. package/dist/components/checkbox-group/index.d.ts +35 -0
  43. package/dist/components/checkbox-group/index.js +99 -0
  44. package/dist/components/checkbox-group/index.js.map +1 -0
  45. package/dist/components/checkbox-group/style.js +6 -0
  46. package/dist/components/checkbox-group/style.js.map +1 -0
  47. package/dist/components/context-menu/index.d.ts +84 -0
  48. package/dist/components/context-menu/index.js +294 -0
  49. package/dist/components/context-menu/index.js.map +1 -0
  50. package/dist/components/context-menu/style.js +6 -0
  51. package/dist/components/context-menu/style.js.map +1 -0
  52. package/dist/components/dialog/index.d.ts +41 -0
  53. package/dist/components/dialog/index.js +118 -0
  54. package/dist/components/dialog/index.js.map +1 -0
  55. package/dist/components/dialog/style.js +6 -0
  56. package/dist/components/dialog/style.js.map +1 -0
  57. package/dist/components/drawer/index.d.ts +57 -0
  58. package/dist/components/drawer/index.js +172 -0
  59. package/dist/components/drawer/index.js.map +1 -0
  60. package/dist/components/drawer/style.js +6 -0
  61. package/dist/components/drawer/style.js.map +1 -0
  62. package/dist/components/dropdown/index.d.ts +87 -0
  63. package/dist/components/dropdown/index.js +324 -0
  64. package/dist/components/dropdown/index.js.map +1 -0
  65. package/dist/components/dropdown/style.js +6 -0
  66. package/dist/components/dropdown/style.js.map +1 -0
  67. package/dist/components/dropdown-divider/index.d.ts +11 -0
  68. package/dist/components/dropdown-divider/index.js +18 -0
  69. package/dist/components/dropdown-divider/index.js.map +1 -0
  70. package/dist/components/dropdown-divider/style.js +6 -0
  71. package/dist/components/dropdown-divider/style.js.map +1 -0
  72. package/dist/components/dropdown-header/index.d.ts +11 -0
  73. package/dist/components/dropdown-header/index.js +18 -0
  74. package/dist/components/dropdown-header/index.js.map +1 -0
  75. package/dist/components/dropdown-header/style.js +6 -0
  76. package/dist/components/dropdown-header/style.js.map +1 -0
  77. package/dist/components/dropdown-item/index.d.ts +17 -0
  78. package/dist/components/dropdown-item/index.js +44 -0
  79. package/dist/components/dropdown-item/index.js.map +1 -0
  80. package/dist/components/dropdown-item/style.js +6 -0
  81. package/dist/components/dropdown-item/style.js.map +1 -0
  82. package/dist/components/empty/index.d.ts +23 -0
  83. package/dist/components/empty/index.js +83 -0
  84. package/dist/components/empty/index.js.map +1 -0
  85. package/dist/components/empty/style.js +6 -0
  86. package/dist/components/empty/style.js.map +1 -0
  87. package/dist/components/icon/index.d.ts +16 -0
  88. package/dist/components/icon/index.js +45 -0
  89. package/dist/components/icon/index.js.map +1 -0
  90. package/dist/components/icon/style.js +6 -0
  91. package/dist/components/icon/style.js.map +1 -0
  92. package/dist/components/index.d.ts +34 -0
  93. package/dist/components/index.js +37 -0
  94. package/dist/components/input/index.d.ts +47 -0
  95. package/dist/components/input/index.js +140 -0
  96. package/dist/components/input/index.js.map +1 -0
  97. package/dist/components/input/style.js +6 -0
  98. package/dist/components/input/style.js.map +1 -0
  99. package/dist/components/input-number/index.d.ts +52 -0
  100. package/dist/components/input-number/index.js +178 -0
  101. package/dist/components/input-number/index.js.map +1 -0
  102. package/dist/components/input-number/style.js +6 -0
  103. package/dist/components/input-number/style.js.map +1 -0
  104. package/dist/components/layout/index.d.ts +11 -1
  105. package/dist/components/layout/index.js +55 -21
  106. package/dist/components/layout/index.js.map +1 -1
  107. package/dist/components/layout/style.js +1 -1
  108. package/dist/components/layout/style.js.map +1 -1
  109. package/dist/components/option/index.d.ts +33 -0
  110. package/dist/components/option/index.js +78 -0
  111. package/dist/components/option/index.js.map +1 -0
  112. package/dist/components/option/style.js +6 -0
  113. package/dist/components/option/style.js.map +1 -0
  114. package/dist/components/popover/index.d.ts +69 -0
  115. package/dist/components/popover/index.js +231 -0
  116. package/dist/components/popover/index.js.map +1 -0
  117. package/dist/components/popover/style.js +6 -0
  118. package/dist/components/popover/style.js.map +1 -0
  119. package/dist/components/radio/index.d.ts +39 -0
  120. package/dist/components/radio/index.js +105 -0
  121. package/dist/components/radio/index.js.map +1 -0
  122. package/dist/components/radio/style.js +6 -0
  123. package/dist/components/radio/style.js.map +1 -0
  124. package/dist/components/radio-group/index.d.ts +35 -0
  125. package/dist/components/radio-group/index.js +87 -0
  126. package/dist/components/radio-group/index.js.map +1 -0
  127. package/dist/components/radio-group/style.js +6 -0
  128. package/dist/components/radio-group/style.js.map +1 -0
  129. package/dist/components/segmented/index.d.ts +33 -0
  130. package/dist/components/segmented/index.js +95 -0
  131. package/dist/components/segmented/index.js.map +1 -0
  132. package/dist/components/segmented/style.js +6 -0
  133. package/dist/components/segmented/style.js.map +1 -0
  134. package/dist/components/segmented-trigger/index.d.ts +18 -0
  135. package/dist/components/segmented-trigger/index.js +52 -0
  136. package/dist/components/segmented-trigger/index.js.map +1 -0
  137. package/dist/components/segmented-trigger/style.js +6 -0
  138. package/dist/components/segmented-trigger/style.js.map +1 -0
  139. package/dist/components/select/index.d.ts +80 -0
  140. package/dist/components/select/index.js +291 -0
  141. package/dist/components/select/index.js.map +1 -0
  142. package/dist/components/select/style.js +6 -0
  143. package/dist/components/select/style.js.map +1 -0
  144. package/dist/components/slider/index.d.ts +61 -0
  145. package/dist/components/slider/index.js +219 -0
  146. package/dist/components/slider/index.js.map +1 -0
  147. package/dist/components/slider/style.js +6 -0
  148. package/dist/components/slider/style.js.map +1 -0
  149. package/dist/components/spinner/index.d.ts +32 -0
  150. package/dist/components/spinner/index.js +52 -0
  151. package/dist/components/spinner/index.js.map +1 -0
  152. package/dist/components/spinner/style.js +6 -0
  153. package/dist/components/spinner/style.js.map +1 -0
  154. package/dist/components/svg-draw-lines/index.d.ts +30 -9
  155. package/dist/components/svg-draw-lines/index.js +87 -62
  156. package/dist/components/svg-draw-lines/index.js.map +1 -1
  157. package/dist/components/svg-draw-lines/style.js +1 -1
  158. package/dist/components/svg-draw-lines/style.js.map +1 -1
  159. package/dist/components/switch/index.d.ts +37 -0
  160. package/dist/components/switch/index.js +117 -0
  161. package/dist/components/switch/index.js.map +1 -0
  162. package/dist/components/switch/style.js +6 -0
  163. package/dist/components/switch/style.js.map +1 -0
  164. package/dist/components/textarea/index.d.ts +65 -0
  165. package/dist/components/textarea/index.js +204 -0
  166. package/dist/components/textarea/index.js.map +1 -0
  167. package/dist/components/textarea/style.js +6 -0
  168. package/dist/components/textarea/style.js.map +1 -0
  169. package/dist/components/theme/index.d.ts +29 -0
  170. package/dist/components/theme/index.js +78 -0
  171. package/dist/components/theme/index.js.map +1 -0
  172. package/dist/components/theme/style.js +6 -0
  173. package/dist/components/theme/style.js.map +1 -0
  174. package/dist/components/toast/index.d.ts +3 -0
  175. package/dist/components/toast/index.js +3 -0
  176. package/dist/components/toast/manager.d.ts +15 -0
  177. package/dist/components/toast/manager.js +98 -0
  178. package/dist/components/toast/manager.js.map +1 -0
  179. package/dist/components/toast/style.js +6 -0
  180. package/dist/components/toast/style.js.map +1 -0
  181. package/dist/components/toast/toast.d.ts +42 -0
  182. package/dist/components/toast/toast.js +122 -0
  183. package/dist/components/toast/toast.js.map +1 -0
  184. package/dist/components/toast/types.d.ts +23 -0
  185. package/dist/components/tooltip/index.d.ts +59 -0
  186. package/dist/components/tooltip/index.js +190 -0
  187. package/dist/components/tooltip/index.js.map +1 -0
  188. package/dist/components/tooltip/style.js +6 -0
  189. package/dist/components/tooltip/style.js.map +1 -0
  190. package/dist/icons/generated/heroicons-check-16-solid.d.ts +6 -0
  191. package/dist/icons/generated/heroicons-check-16-solid.js +10 -0
  192. package/dist/icons/generated/heroicons-check-16-solid.js.map +1 -0
  193. package/dist/icons/generated/heroicons-x-mark-16-solid.d.ts +6 -0
  194. package/dist/icons/generated/heroicons-x-mark-16-solid.js +10 -0
  195. package/dist/icons/generated/heroicons-x-mark-16-solid.js.map +1 -0
  196. package/dist/icons/generated/jam-close-circle-f.d.ts +8 -0
  197. package/dist/icons/generated/jam-close-circle-f.js +12 -0
  198. package/dist/icons/generated/jam-close-circle-f.js.map +1 -0
  199. package/dist/icons/generated/jam-volume.d.ts +8 -0
  200. package/dist/icons/generated/jam-watch-f.d.ts +8 -0
  201. package/dist/icons/generated/lucide-arrow-down-to-line.d.ts +6 -0
  202. package/dist/icons/generated/lucide-arrow-down-to-line.js +10 -0
  203. package/dist/icons/generated/lucide-arrow-down-to-line.js.map +1 -0
  204. package/dist/icons/generated/lucide-arrow-up-to-line.d.ts +6 -0
  205. package/dist/icons/generated/lucide-arrow-up-to-line.js +10 -0
  206. package/dist/icons/generated/lucide-arrow-up-to-line.js.map +1 -0
  207. package/dist/icons/generated/lucide-bell.d.ts +6 -0
  208. package/dist/icons/generated/lucide-bell.js +10 -0
  209. package/dist/icons/generated/lucide-bell.js.map +1 -0
  210. package/dist/icons/generated/lucide-check.d.ts +6 -0
  211. package/dist/icons/generated/lucide-check.js +10 -0
  212. package/dist/icons/generated/lucide-check.js.map +1 -0
  213. package/dist/icons/generated/lucide-chevron-down.d.ts +6 -0
  214. package/dist/icons/generated/lucide-chevron-down.js +10 -0
  215. package/dist/icons/generated/lucide-chevron-down.js.map +1 -0
  216. package/dist/icons/generated/lucide-chevron-left.d.ts +6 -0
  217. package/dist/icons/generated/lucide-chevron-left.js +10 -0
  218. package/dist/icons/generated/lucide-chevron-left.js.map +1 -0
  219. package/dist/icons/generated/lucide-chevron-right.d.ts +6 -0
  220. package/dist/icons/generated/lucide-chevron-right.js +10 -0
  221. package/dist/icons/generated/lucide-chevron-right.js.map +1 -0
  222. package/dist/icons/generated/lucide-chevron-up.d.ts +6 -0
  223. package/dist/icons/generated/lucide-chevron-up.js +10 -0
  224. package/dist/icons/generated/lucide-chevron-up.js.map +1 -0
  225. package/dist/icons/generated/lucide-circle-alert.d.ts +6 -0
  226. package/dist/icons/generated/lucide-circle-alert.js +10 -0
  227. package/dist/icons/generated/lucide-circle-alert.js.map +1 -0
  228. package/dist/icons/generated/lucide-clipboard-paste.d.ts +6 -0
  229. package/dist/icons/generated/lucide-clipboard-paste.js +10 -0
  230. package/dist/icons/generated/lucide-clipboard-paste.js.map +1 -0
  231. package/dist/icons/generated/lucide-copy.d.ts +6 -0
  232. package/dist/icons/generated/lucide-copy.js +10 -0
  233. package/dist/icons/generated/lucide-copy.js.map +1 -0
  234. package/dist/icons/generated/lucide-download.d.ts +6 -0
  235. package/dist/icons/generated/lucide-download.js +10 -0
  236. package/dist/icons/generated/lucide-download.js.map +1 -0
  237. package/dist/icons/generated/lucide-file.d.ts +6 -0
  238. package/dist/icons/generated/lucide-file.js +10 -0
  239. package/dist/icons/generated/lucide-file.js.map +1 -0
  240. package/dist/icons/generated/lucide-folder.d.ts +6 -0
  241. package/dist/icons/generated/lucide-folder.js +10 -0
  242. package/dist/icons/generated/lucide-folder.js.map +1 -0
  243. package/dist/icons/generated/lucide-globe.d.ts +6 -0
  244. package/dist/icons/generated/lucide-globe.js +10 -0
  245. package/dist/icons/generated/lucide-globe.js.map +1 -0
  246. package/dist/icons/generated/lucide-heart.d.ts +6 -0
  247. package/dist/icons/generated/lucide-heart.js +10 -0
  248. package/dist/icons/generated/lucide-heart.js.map +1 -0
  249. package/dist/icons/generated/lucide-image.d.ts +6 -0
  250. package/dist/icons/generated/lucide-image.js +10 -0
  251. package/dist/icons/generated/lucide-image.js.map +1 -0
  252. package/dist/icons/generated/lucide-inbox.d.ts +6 -0
  253. package/dist/icons/generated/lucide-inbox.js +10 -0
  254. package/dist/icons/generated/lucide-inbox.js.map +1 -0
  255. package/dist/icons/generated/lucide-info.d.ts +6 -0
  256. package/dist/icons/generated/lucide-info.js +10 -0
  257. package/dist/icons/generated/lucide-info.js.map +1 -0
  258. package/dist/icons/generated/lucide-loader-circle.d.ts +6 -0
  259. package/dist/icons/generated/lucide-loader-circle.js +10 -0
  260. package/dist/icons/generated/lucide-loader-circle.js.map +1 -0
  261. package/dist/icons/generated/lucide-mail.d.ts +6 -0
  262. package/dist/icons/generated/lucide-mail.js +10 -0
  263. package/dist/icons/generated/lucide-mail.js.map +1 -0
  264. package/dist/icons/generated/lucide-minus.d.ts +6 -0
  265. package/dist/icons/generated/lucide-minus.js +10 -0
  266. package/dist/icons/generated/lucide-minus.js.map +1 -0
  267. package/dist/icons/generated/lucide-plus.d.ts +6 -0
  268. package/dist/icons/generated/lucide-plus.js +10 -0
  269. package/dist/icons/generated/lucide-plus.js.map +1 -0
  270. package/dist/icons/generated/lucide-printer.d.ts +6 -0
  271. package/dist/icons/generated/lucide-printer.js +10 -0
  272. package/dist/icons/generated/lucide-printer.js.map +1 -0
  273. package/dist/icons/generated/lucide-redo-2.d.ts +6 -0
  274. package/dist/icons/generated/lucide-redo-2.js +10 -0
  275. package/dist/icons/generated/lucide-redo-2.js.map +1 -0
  276. package/dist/icons/generated/lucide-scissors.d.ts +6 -0
  277. package/dist/icons/generated/lucide-scissors.js +10 -0
  278. package/dist/icons/generated/lucide-scissors.js.map +1 -0
  279. package/dist/icons/generated/lucide-search.d.ts +6 -0
  280. package/dist/icons/generated/lucide-search.js +10 -0
  281. package/dist/icons/generated/lucide-search.js.map +1 -0
  282. package/dist/icons/generated/lucide-settings.d.ts +6 -0
  283. package/dist/icons/generated/lucide-settings.js +10 -0
  284. package/dist/icons/generated/lucide-settings.js.map +1 -0
  285. package/dist/icons/generated/lucide-star.d.ts +6 -0
  286. package/dist/icons/generated/lucide-star.js +10 -0
  287. package/dist/icons/generated/lucide-star.js.map +1 -0
  288. package/dist/icons/generated/lucide-trash-2.d.ts +6 -0
  289. package/dist/icons/generated/lucide-trash-2.js +10 -0
  290. package/dist/icons/generated/lucide-trash-2.js.map +1 -0
  291. package/dist/icons/generated/lucide-triangle-alert.d.ts +6 -0
  292. package/dist/icons/generated/lucide-triangle-alert.js +10 -0
  293. package/dist/icons/generated/lucide-triangle-alert.js.map +1 -0
  294. package/dist/icons/generated/lucide-undo-2.d.ts +6 -0
  295. package/dist/icons/generated/lucide-undo-2.js +10 -0
  296. package/dist/icons/generated/lucide-undo-2.js.map +1 -0
  297. package/dist/icons/generated/lucide-upload.d.ts +6 -0
  298. package/dist/icons/generated/lucide-upload.js +10 -0
  299. package/dist/icons/generated/lucide-upload.js.map +1 -0
  300. package/dist/icons/generated/lucide-user.d.ts +6 -0
  301. package/dist/icons/generated/lucide-user.js +10 -0
  302. package/dist/icons/generated/lucide-user.js.map +1 -0
  303. package/dist/icons/generated/lucide-x.d.ts +6 -0
  304. package/dist/icons/generated/lucide-x.js +10 -0
  305. package/dist/icons/generated/lucide-x.js.map +1 -0
  306. package/dist/icons/generated/ooui-check.d.ts +6 -0
  307. package/dist/icons/generated/radix-icons-check.d.ts +6 -0
  308. package/dist/icons/generated/radix-icons-panel-left-minimized.d.ts +6 -0
  309. package/dist/icons/generated/radix-icons-panel-left-minimized.js +10 -0
  310. package/dist/icons/generated/radix-icons-panel-left-minimized.js.map +1 -0
  311. package/dist/icons/generated/si-check-alt-fill.d.ts +6 -0
  312. package/dist/icons/index.d.ts +39 -0
  313. package/dist/icons/index.js +40 -0
  314. package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js +248 -0
  315. package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js.map +1 -0
  316. package/dist/node_modules/.pnpm/@floating-ui_dom@1.8.0/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +324 -0
  317. package/dist/node_modules/.pnpm/@floating-ui_dom@1.8.0/node_modules/@floating-ui/dom/dist/floating-ui.dom.js.map +1 -0
  318. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +99 -0
  319. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js.map +1 -0
  320. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +102 -0
  321. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.js.map +1 -0
  322. package/dist/shared/menu-portal/menu-portal.js +11 -0
  323. package/dist/shared/menu-portal/menu-portal.js.map +1 -0
  324. package/dist/shared/normalize/index.js +11 -0
  325. package/dist/shared/normalize/index.js.map +1 -0
  326. package/dist/shared/overlay/overlay.js +68 -0
  327. package/dist/shared/overlay/overlay.js.map +1 -0
  328. package/dist/shared/overlay/portal.js +29 -0
  329. package/dist/shared/overlay/portal.js.map +1 -0
  330. package/dist/shared/overlay/presence.js +34 -0
  331. package/dist/shared/overlay/presence.js.map +1 -0
  332. package/dist/shared/property-converters/boolean-with-false-string.js +9 -0
  333. package/dist/shared/property-converters/boolean-with-false-string.js.map +1 -0
  334. package/dist/shared/scroll-lock/scroll-lock.js +12 -0
  335. package/dist/shared/scroll-lock/scroll-lock.js.map +1 -0
  336. package/dist/shared/theme/overlay-root.js +28 -0
  337. package/dist/shared/theme/overlay-root.js.map +1 -0
  338. package/dist/shared/theme/theme-scope.js +27 -0
  339. package/dist/shared/theme/theme-scope.js.map +1 -0
  340. package/dist/types/iconify.d.ts +1 -0
  341. package/dist/types/react.d.ts +35 -9
  342. package/dist/types/utils.d.ts +14 -0
  343. package/dist/types/vue.d.ts +47 -7
  344. package/package.json +29 -13
  345. package/dist/index.d.ts +0 -4
  346. package/dist/index.js +0 -5
package/README.md CHANGED
@@ -4,32 +4,13 @@
4
4
 
5
5
  English | [简体中文](./README.CN.md)
6
6
 
7
- ## Features
8
-
9
- - **Button**: Styled button with full/primary/text/icon variants
10
- - **BackTop**: Scroll-to-top with configurable threshold and smooth scroll
11
- - **Layout**: Page layout with header/sidebar/tabbar slots
12
- - **SVG Draw Lines**: SVG line drawing animation with easing control
13
- - **Framework compatible**: Works with React, Vue, and vanilla HTML
14
- - **Type safe**: Full TypeScript types for React and Vue
15
-
16
7
  ## Install
17
8
 
18
9
  ```bash
19
- # npm
20
10
  npm install @greypan/web-ui
21
-
22
- # pnpm
23
- pnpm add @greypan/web-ui
24
-
25
- # yarn
26
- yarn add @greypan/web-ui
27
-
28
- # bun
29
- bun add @greypan/web-ui
30
11
  ```
31
12
 
32
- > Requires `lit` as a dependency.
13
+ Requires `lit` as a dependency.
33
14
 
34
15
  ## Quick Start
35
16
 
@@ -39,133 +20,45 @@ import '@greypan/web-ui'
39
20
  ```
40
21
 
41
22
  ```html
42
- <web-ui-button primary>Click me</web-ui-button>
43
- <web-ui-button text>Cancel</web-ui-button>
44
- <web-ui-button icon>
45
- <iconify-icon icon="lucide:plus"></iconify-icon>
46
- </web-ui-button>
23
+ <web-ui-button variant="primary">Click me</web-ui-button> <web-ui-icon .icon="${lucidePlus}"></web-ui-icon>
47
24
  ```
48
25
 
49
- ## React
50
-
51
- > Requires `@types/react >= 16` as an optional peer dependency for type support.
26
+ ## Framework Setup
52
27
 
53
- ### Setup
28
+ ### React
54
29
 
55
- **Option A — auto-import (Vite, recommended)**
56
-
57
- Use `unplugin-web-components` to auto-register components when their tags appear in JSX:
30
+ Requires `@types/react >= 16` as optional peer dependency.
58
31
 
59
32
  ```ts
60
33
  // vite.config.ts
61
34
  import unpluginWebComponents from '@greypan/unplugin-web-components/vite'
62
35
 
63
36
  export default {
64
- plugins: [
65
- unpluginWebComponents({
66
- tagPrefix: 'web-ui',
67
- packageName: '@greypan/web-ui',
68
- sideEffects: true
69
- })
70
- ]
37
+ plugins: [unpluginWebComponents({ tagPrefix: 'web-ui', packageName: '@greypan/web-ui', sideEffects: true })]
71
38
  }
72
- ```
73
-
74
- **Option B — manual import**
75
-
76
- Import the full library to register all components (side-effect registration):
77
39
 
78
- ```tsx
79
- // main.tsx
80
- import '@greypan/web-ui'
81
- ```
82
-
83
- Or import only specific components for smaller bundle:
84
-
85
- ```tsx
86
- // main.tsx or any component file
87
- import '@greypan/web-ui/components/button'
88
- import '@greypan/web-ui/components/back-top'
89
- ```
90
-
91
- Add type augmentation so TypeScript recognizes the custom element JSX attributes:
92
-
93
- ```tsx
94
- // env.d.ts or any type declaration file
40
+ // env.d.ts
95
41
  import '@greypan/web-ui/types/react'
96
42
  ```
97
43
 
98
- ### App Layout
99
-
100
- Wrap your router with `<web-ui-layout>` and use `<web-ui-back-top>` for global scroll-to-top:
101
-
102
44
  ```tsx
103
- // root.tsx
104
- import { Outlet } from '@tanstack/react-router'
45
+ import '@greypan/web-ui'
105
46
 
106
- export function Root() {
47
+ function App() {
107
48
  return (
108
49
  <>
109
- <web-ui-layout>
110
- <h1>My App</h1>
111
-
112
- {/* Button variants */}
113
- <div className="flex gap-2">
114
- <web-ui-button>Default</web-ui-button>
115
- <web-ui-button primary>Primary</web-ui-button>
116
- <web-ui-button text>Text</web-ui-button>
117
- <web-ui-button full>Full Width</web-ui-button>
118
- </div>
119
-
120
- {/* Named slots */}
121
- <web-ui-button>
122
- <span slot="prefix">prefix</span>
123
- Button with prefix/suffix slots
124
- <span slot="suffix">suffix</span>
125
- </web-ui-button>
126
-
127
- {/* Router outlet for page content */}
128
- <Outlet />
129
- </web-ui-layout>
130
-
131
- <web-ui-back-top
132
- threshold={300}
133
- onvisible-change={e => {
134
- console.log('visible: ', e.detail.visible)
135
- }}
136
- />
50
+ <web-ui-button variant="primary" onClick={() => alert('clicked')}>
51
+ Click
52
+ </web-ui-button>
53
+ <web-ui-input onInput={e => console.log((e.target as any).value)} />
137
54
  </>
138
55
  )
139
56
  }
140
57
  ```
141
58
 
142
- > `onvisible-change` works in React 19+ (props forward to custom elements). For React 18, use a `ref` + `addEventListener` instead.
143
-
144
- ### SVG Draw Lines
145
-
146
- Wrap an inline `<svg>` to animate its stroke drawing:
147
-
148
- ```tsx
149
- function SvgDemo() {
150
- return (
151
- <web-ui-svg-draw-lines>
152
- <svg viewBox="0 0 24 24" width="100" height="100" fill="none" stroke="currentColor" stroke-width="2">
153
- <path d="M4 4v16M8 6h12M8 12h6m-6 6h10" />
154
- </svg>
155
- </web-ui-svg-draw-lines>
156
- )
157
- }
158
- ```
159
-
160
- ## Vue
161
-
162
- > Requires `vue >= 3` as an optional peer dependency.
163
-
164
- ### Setup
165
-
166
- **Option A — auto-import (Vite, recommended)**
59
+ ### Vue
167
60
 
168
- Use `unplugin-web-components` for auto-registration:
61
+ Requires `vue >= 3` as optional peer dependency.
169
62
 
170
63
  ```ts
171
64
  // vite.config.ts
@@ -175,155 +68,708 @@ import unpluginWebComponents from '@greypan/unplugin-web-components/vite'
175
68
  export default {
176
69
  plugins: [
177
70
  vue({
178
- template: {
179
- compilerOptions: {
180
- // tell Vue's template compiler to treat web-ui-* tags as custom elements
181
- isCustomElement: tag => tag.startsWith('web-ui-') || tag.startsWith('WebUi')
182
- }
183
- }
71
+ template: { compilerOptions: { isCustomElement: tag => tag.startsWith('web-ui-') } }
184
72
  }),
185
- unpluginWebComponents({
186
- tagPrefix: 'web-ui',
187
- packageName: '@greypan/web-ui',
188
- sideEffects: true
189
- })
73
+ unpluginWebComponents({ tagPrefix: 'web-ui', packageName: '@greypan/web-ui', sideEffects: true })
190
74
  ]
191
75
  }
76
+
77
+ // env.d.ts
78
+ import '@greypan/web-ui/types/vue'
192
79
  ```
193
80
 
194
- **Option B — manual import**
81
+ ```vue
82
+ <template>
83
+ <web-ui-button variant="primary" @click="handleClick">Click</web-ui-button>
84
+ <web-ui-input v-model="value" />
85
+ </template>
86
+ ```
195
87
 
196
- ```ts
197
- // main.ts
198
- import { createApp } from 'vue'
199
- import '@greypan/web-ui' // register all components
200
- // import '@greypan/web-ui/components/button'
201
- import App from './App.vue'
88
+ ## All Components
89
+
90
+ | Category | Component |
91
+ | -------------------- | --------------------------------------------------------- |
92
+ | **Form Controls** | [`<web-ui-input>`](#web-ui-input) |
93
+ | | [`<web-ui-textarea>`](#web-ui-textarea) |
94
+ | | [`<web-ui-input-number>`](#web-ui-input-number) |
95
+ | | [`<web-ui-select>`](#web-ui-select) |
96
+ | | [`<web-ui-slider>`](#web-ui-slider) |
97
+ | | [`<web-ui-checkbox>`](#web-ui-checkbox) |
98
+ | | [`<web-ui-radio>`](#web-ui-radio) |
99
+ | | [`<web-ui-switch>`](#web-ui-switch) |
100
+ | | [`<web-ui-segmented>`](#web-ui-segmented) |
101
+ | | [`<web-ui-checkbox-group>`](#web-ui-checkbox-group) |
102
+ | | [`<web-ui-radio-group>`](#web-ui-radio-group) |
103
+ | **Buttons** | [`<web-ui-button>`](#web-ui-button) |
104
+ | | [`<web-ui-button-group>`](#web-ui-button-group) |
105
+ | **Overlay / Modal** | [`<web-ui-dialog>`](#web-ui-dialog) |
106
+ | | [`<web-ui-drawer>`](#web-ui-drawer) |
107
+ | **Floating** | [`<web-ui-popover>`](#web-ui-popover) |
108
+ | | [`<web-ui-tooltip>`](#web-ui-tooltip) |
109
+ | | [`<web-ui-context-menu>`](#web-ui-context-menu) |
110
+ | **Menu** | [`<web-ui-dropdown>`](#web-ui-dropdown) |
111
+ | | [`<web-ui-dropdown-item>`](#web-ui-dropdown-item) |
112
+ | | [`<web-ui-dropdown-divider>`](#web-ui-dropdown-divider) |
113
+ | | [`<web-ui-dropdown-header>`](#web-ui-dropdown-header) |
114
+ | **Data Display** | [`<web-ui-avatar>`](#web-ui-avatar) |
115
+ | | [`<web-ui-badge>`](#web-ui-badge) |
116
+ | | [`<web-ui-empty>`](#web-ui-empty) |
117
+ | | [`<web-ui-icon>`](#web-ui-icon) |
118
+ | | [`<web-ui-spinner>`](#web-ui-spinner) |
119
+ | **Layout & Utility** | [`<web-ui-layout>`](#web-ui-layout) |
120
+ | | [`<web-ui-back-top>`](#web-ui-back-top) |
121
+ | | [`<web-ui-svg-draw-lines>`](#web-ui-svg-draw-lines) |
122
+ | | [`<web-ui-theme>`](#web-ui-theme) |
123
+ | **Notification** | [`<web-ui-toast>`](#web-ui-toast) |
124
+ | **Sub-items** | [`<web-ui-option>`](#web-ui-option) |
125
+ | | [`<web-ui-segmented-trigger>`](#web-ui-segmented-trigger) |
126
+
127
+ ## API Reference
128
+
129
+ ### Form Controls
130
+
131
+ Form controls implement `static formAssociated = true` and integrate with native `<form>`: values are submitted via `FormData`, and `formResetCallback()` / `formDisabledCallback()` handle lifecycle.
132
+
133
+ #### `<web-ui-input>`
134
+
135
+ Text input with clearable, prefix/suffix slots.
136
+
137
+ | Attribute | Type | Default | Description |
138
+ | ------------- | --------- | -------- | ------------------- |
139
+ | `value` | `string` | `''` | Input value |
140
+ | `type` | `string` | `'text'` | HTML input type |
141
+ | `placeholder` | `string` | `''` | Placeholder text |
142
+ | `name` | `string` | `''` | Form field name |
143
+ | `disabled` | `boolean` | `false` | Disabled state |
144
+ | `required` | `boolean` | `false` | Required validation |
145
+ | `clearable` | `boolean` | `false` | Show clear button |
146
+ | `full` | `boolean` | `false` | Full width |
147
+ | `borderless` | `boolean` | `false` | No border |
148
+
149
+ **Events:** `input`, `change`, `focus`, `blur`
150
+
151
+ **Slots:** `prefix`, `default`, `suffix`
152
+
153
+ #### `<web-ui-textarea>`
154
+
155
+ Multi-line text input with auto-resize.
156
+
157
+ | Attribute | Type | Default | Description |
158
+ | ----------------- | --------- | ------- | -------------------------- |
159
+ | `value` | `string` | `''` | Textarea value |
160
+ | `placeholder` | `string` | `''` | Placeholder text |
161
+ | `rows` | `number` | `3` | Visible rows |
162
+ | `name` | `string` | `''` | Form field name |
163
+ | `disabled` | `boolean` | `false` | Disabled state |
164
+ | `readonly` | `boolean` | `false` | Read-only state |
165
+ | `required` | `boolean` | `false` | Required validation |
166
+ | `clearable` | `boolean` | `false` | Show clear button |
167
+ | `full` | `boolean` | `false` | Full width |
168
+ | `borderless` | `boolean` | `false` | No border |
169
+ | `autosize` | `boolean` | `false` | Auto-resize height |
170
+ | `minlength` | `number` | — | Minimum length validation |
171
+ | `maxlength` | `number` | — | Maximum length validation |
172
+ | `aria-label` | `string` | — | Accessible label |
173
+ | `aria-labelledby` | `string` | — | Accessible label reference |
174
+
175
+ **Events:** `input`, `change`, `focus`, `blur`
176
+
177
+ **Methods:** `focus()`, `blur()`, `select()`
178
+
179
+ **Slots:** `prefix`, `suffix`
180
+
181
+ #### `<web-ui-input-number>`
182
+
183
+ Numeric input with step buttons and keyboard control.
184
+
185
+ | Attribute | Type | Default | Description |
186
+ | ------------- | --------- | ---------- | ------------------- |
187
+ | `value` | `number` | `0` | Current value |
188
+ | `min` | `number` | `0` | Minimum value |
189
+ | `max` | `number` | `Infinity` | Maximum value |
190
+ | `step` | `number` | `1` | Step increment |
191
+ | `precision` | `number` | `0` | Decimal precision |
192
+ | `placeholder` | `string` | `''` | Placeholder text |
193
+ | `name` | `string` | `''` | Form field name |
194
+ | `disabled` | `boolean` | `false` | Disabled state |
195
+ | `required` | `boolean` | `false` | Required validation |
196
+
197
+ **Events:** `input`, `change`
202
198
 
203
- createApp(App).mount('#app')
204
- ```
199
+ ArrowUp/ArrowDown keyboard increments and decrements the value.
205
200
 
206
- And configure the Vue compiler to recognize custom elements:
201
+ #### `<web-ui-select>`
207
202
 
208
- ```ts
209
- // vite.config.ts
210
- vue({
211
- template: {
212
- compilerOptions: {
213
- isCustomElement: tag => tag.startsWith('web-ui-') || tag.startsWith('WebUi')
214
- }
215
- }
216
- })
217
- ```
203
+ Select dropdown with option items, keyboard navigation, and portal support.
218
204
 
219
- For type augmentation in Vue templates:
205
+ | Attribute | Type | Default | Description |
206
+ | ------------------ | ---------------------------------- | ------- | -------------------------------- |
207
+ | `value` | `string` | `''` | Selected value |
208
+ | `placeholder` | `string` | `''` | Placeholder text |
209
+ | `name` | `string` | `''` | Form field name |
210
+ | `disabled` | `boolean` | `false` | Disabled state |
211
+ | `required` | `boolean` | `false` | Required validation |
212
+ | `portal` | `boolean` | `false` | Render dropdown in theme overlay |
213
+ | `lock-scroll` | `boolean` | `true` | Lock body scroll when open |
214
+ | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
220
215
 
221
- ```ts
222
- // env.d.ts or any type declaration file
223
- import '@greypan/web-ui/types/vue'
224
- ```
216
+ **Events:** `input`, `change`, `open-change` (`CustomEvent<{ open: boolean }>`)
225
217
 
226
- ### App Layout
218
+ **Slots:** `default` (project `<web-ui-option>` elements), `trigger` (custom trigger content — replaces the default label and chevron area)
227
219
 
228
- Wrap your app with `<web-ui-layout>` and use the button variants:
220
+ **Methods:** none
229
221
 
230
- ```vue
231
- <template>
232
- <web-ui-layout>
233
- <h1>My App</h1>
234
-
235
- <div class="flex gap-2">
236
- <web-ui-button>默认</web-ui-button>
237
- <web-ui-button primary>Primary</web-ui-button>
238
- <web-ui-button text>Text</web-ui-button>
239
- <web-ui-button full>Full Width</web-ui-button>
240
- </div>
241
-
242
- <web-ui-button>
243
- <span slot="prefix">prefix</span>
244
- 带前后缀插槽的按钮
245
- <span slot="suffix">suffix</span>
246
- </web-ui-button>
247
-
248
- <RouterView />
249
- </web-ui-layout>
250
-
251
- <web-ui-back-top :threshold="300" @visible-change="onVisibleChange" />
252
- </template>
222
+ Child `<web-ui-option>` elements register via `option-register` / `option-unregister` events. Supports ArrowDown/ArrowUp/Enter/Escape keyboard navigation.
253
223
 
254
- <script setup lang="ts">
255
- function onVisibleChange(e: CustomEvent<{ visible: boolean }>) {
256
- console.log('visible: ', e.detail.visible)
257
- }
258
- </script>
224
+ #### `<web-ui-slider>`
225
+
226
+ Range slider with marks and vertical orientation.
227
+
228
+ | Attribute | Type | Default | Description |
229
+ | ---------- | --------- | ------- | -------------------- |
230
+ | `value` | `number` | `0` | Current value |
231
+ | `min` | `number` | `0` | Minimum value |
232
+ | `max` | `number` | `100` | Maximum value |
233
+ | `step` | `number` | `1` | Step increment |
234
+ | `name` | `string` | `''` | Form field name |
235
+ | `disabled` | `boolean` | `false` | Disabled state |
236
+ | `required` | `boolean` | `false` | Required validation |
237
+ | `marks` | `boolean` | `false` | Show tick marks |
238
+ | `vertical` | `boolean` | `false` | Vertical orientation |
239
+
240
+ **Events:** `input` (during drag), `change` (on release or keyboard)
241
+
242
+ **Methods:** `focus()`, `blur()`
243
+
244
+ Supports ArrowLeft/Right/Up/Down, Home/End, PageUp/PageDown keyboard navigation. Uses pointer capture for drag interaction across mouse, touch, and pen.
245
+
246
+ #### `<web-ui-checkbox>`
247
+
248
+ Individual checkbox.
249
+
250
+ | Attribute | Type | Default | Description |
251
+ | ---------- | --------- | ------- | --------------------- |
252
+ | `checked` | `boolean` | `false` | Checked state |
253
+ | `value` | `string` | `''` | Form submission value |
254
+ | `name` | `string` | `''` | Form field name |
255
+ | `disabled` | `boolean` | `false` | Disabled state |
256
+ | `required` | `boolean` | `false` | Required validation |
257
+
258
+ **Events:** `input`, `change`
259
+
260
+ **Slots:** `default` (label text)
261
+
262
+ Uses native `<label>` with `role="checkbox"` and `aria-checked`. Enter/Space keyboard toggle.
263
+
264
+ #### `<web-ui-radio>`
265
+
266
+ Individual radio button.
267
+
268
+ | Attribute | Type | Default | Description |
269
+ | ---------- | --------- | ------- | --------------------- |
270
+ | `checked` | `boolean` | `false` | Checked state |
271
+ | `value` | `string` | `''` | Form submission value |
272
+ | `name` | `string` | `''` | Form field name |
273
+ | `disabled` | `boolean` | `false` | Disabled state |
274
+ | `required` | `boolean` | `false` | Required validation |
275
+
276
+ **Events:** `input`, `change`
277
+
278
+ **Slots:** `default` (label text)
279
+
280
+ #### `<web-ui-switch>`
281
+
282
+ Toggle switch.
283
+
284
+ | Attribute | Type | Default | Description |
285
+ | ---------- | --------- | ------- | ----------------------- |
286
+ | `checked` | `boolean` | `false` | Checked (on) state |
287
+ | `value` | `string` | `''` | Form submission value |
288
+ | `name` | `string` | `''` | Form field name |
289
+ | `disabled` | `boolean` | `false` | Disabled state |
290
+ | `required` | `boolean` | `false` | Required validation |
291
+ | `loading` | `boolean` | `false` | Loading (spinner) state |
292
+
293
+ **Events:** `input`, `change`
294
+
295
+ **Slots:** none
296
+
297
+ Uses `role="switch"` and `aria-checked`. Pointer events for pressed visual state.
298
+
299
+ #### `<web-ui-segmented>`
300
+
301
+ Segmented control — single-select button group.
302
+
303
+ | Attribute | Type | Default | Description |
304
+ | ---------- | --------- | ------- | ---------------------- |
305
+ | `value` | `string` | `''` | Selected trigger value |
306
+ | `name` | `string` | `''` | Form field name |
307
+ | `disabled` | `boolean` | `false` | Disabled state |
308
+ | `required` | `boolean` | `false` | Required validation |
309
+
310
+ **Events:** `input`, `change`
311
+
312
+ **Slots:** `default` (project `<web-ui-segmented-trigger>` elements)
313
+
314
+ Form-associated: integrates with native `<form>` via `ElementInternals`.
315
+
316
+ Manages child trigger `checked` state based on `value`. Setting `value` directly does not dispatch `input`/`change`.
317
+
318
+ #### `<web-ui-checkbox-group>`
319
+
320
+ Checkbox group managing multiple selection.
321
+
322
+ | Attribute | Type | Default | Description |
323
+ | ---------- | ---------- | ------- | --------------------------------------- |
324
+ | `value` | `string[]` | `[]` | Selected values |
325
+ | `name` | `string` | `''` | Form field name |
326
+ | `disabled` | `boolean` | `false` | Disabled state (propagates to children) |
327
+ | `required` | `boolean` | `false` | Required validation |
328
+
329
+ **Events:** `input`, `change`
330
+
331
+ **Slots:** `default` (project `<web-ui-checkbox>` elements)
332
+
333
+ Syncs with child checkbox `checked`/`disabled` via public property access. Listens to child `change` events.
334
+
335
+ #### `<web-ui-radio-group>`
336
+
337
+ Radio group managing single selection.
338
+
339
+ | Attribute | Type | Default | Description |
340
+ | ---------- | --------- | ------- | ---------------------------------------- |
341
+ | `value` | `string` | `''` | Selected radio value |
342
+ | `name` | `string` | `''` | Form field name (propagates to children) |
343
+ | `disabled` | `boolean` | `false` | Disabled state (propagates to children) |
344
+ | `required` | `boolean` | `false` | Required validation |
345
+
346
+ **Events:** `input`, `change`
347
+
348
+ **Slots:** `default` (project `<web-ui-radio>` elements)
349
+
350
+ ---
351
+
352
+ ### Buttons
353
+
354
+ #### `<web-ui-button>`
355
+
356
+ Styled button with variants and loading state.
357
+
358
+ | Attribute | Type | Default | Description |
359
+ | ---------- | ------------------------------------------------------------ | --------- | -------------------------------------- |
360
+ | `variant` | `'primary' \| 'secondary' \| 'ghost' \| 'danger' \| 'glass'` | `'glass'` | Button variant |
361
+ | `disabled` | `boolean` | `false` | Disabled state |
362
+ | `loading` | `boolean` | `false` | Loading spinner |
363
+ | `full` | `boolean` | `false` | Full width |
364
+ | `icon` | `boolean` | `false` | Icon-only mode |
365
+ | `size` | `string` | `''` | Size format `height` or `heightxwidth` |
366
+
367
+ **Events:** standard `click`
368
+
369
+ **Slots:** `prefix`, `default`, `suffix`
370
+
371
+ Disabled and loading states prevent `click` events.
372
+
373
+ #### `<web-ui-button-group>`
374
+
375
+ Button group that manages child button layout and direction.
376
+
377
+ | Attribute | Type | Default | Description |
378
+ | ----------- | ---------------------------- | -------------- | ---------------- |
379
+ | `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction |
380
+
381
+ **Slots:** `default` (project `<web-ui-button>` elements)
382
+
383
+ Propagates `direction` attribute to child buttons.
384
+
385
+ ---
386
+
387
+ ### Overlay / Modal
388
+
389
+ #### `<web-ui-dialog>`
390
+
391
+ Modal dialog using native `<dialog>` with `showModal()`.
392
+
393
+ | Attribute | Type | Default | Description |
394
+ | ------------------ | --------- | ------- | -------------------------- |
395
+ | `open` | `boolean` | `false` | Dialog visibility |
396
+ | `lock-scroll` | `boolean` | `true` | Lock body scroll when open |
397
+ | `overlay-closable` | `boolean` | `true` | Close on backdrop click |
398
+
399
+ **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
400
+
401
+ **Slots:** `body`, `title`, `default`, `footer`
402
+
403
+ **Methods:** `showModal()`, `close()`
404
+
405
+ Uses native `<dialog>` with `@cancel` prevention (Escape calls `close()`). Click on backdrop closes dialog.
406
+
407
+ #### `<web-ui-drawer>`
408
+
409
+ Side drawer using native `<dialog>` with closing animation.
410
+
411
+ | Attribute | Type | Default | Description |
412
+ | ------------------ | ---------------------------------------- | --------- | ----------------------------------------- |
413
+ | `open` | `boolean` | `false` | Drawer visibility |
414
+ | `placement` | `'right' \| 'left' \| 'top' \| 'bottom'` | `'right'` | Slide-in direction |
415
+ | `heading` | `string` | `''` | Title text (fallback when no header slot) |
416
+ | `closable` | `boolean` | `false` | Show close button |
417
+ | `lock-scroll` | `boolean` | `true` | Lock body scroll when open |
418
+ | `overlay-closable` | `boolean` | `true` | Close on backdrop click |
419
+
420
+ **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
421
+
422
+ **Slots:** `header`, `default`, `footer`
423
+
424
+ **Methods:** `show()`, `close()`
425
+
426
+ Closing keeps the native dialog in the top layer until the `--wui-duration-drawer` transition completes (280ms by default), then calls `dialog.close()`. Escape always follows this close path; `overlay-closable` controls backdrop clicks only.
427
+
428
+ ---
429
+
430
+ ### Floating
431
+
432
+ #### `<web-ui-popover>`
433
+
434
+ Popover overlay anchored to trigger element.
435
+
436
+ | Attribute | Type | Default | Description |
437
+ | ------------------ | ---------------------------------- | ---------- | ------------------------- |
438
+ | `open` | `boolean` | `false` | Popover visibility |
439
+ | `disabled` | `boolean` | `false` | Disabled state |
440
+ | `placement` | `Placement` | `'bottom'` | Floating UI placement |
441
+ | `trigger` | `'click' \| 'hover' \| 'manual'` | `'click'` | Open trigger |
442
+ | `offset` | `number` | `8` | Offset from anchor |
443
+ | `portal` | `boolean` | `false` | Render in theme overlay |
444
+ | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
445
+
446
+ **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
447
+
448
+ **Slots:** `trigger`, `default`
449
+
450
+ **Methods:** `show()`, `close()`, `toggle()`
451
+
452
+ Hover mode uses `pointerenter`/`pointerleave` with delay. Click mode toggles on trigger click. Manual mode only responds to imperative `show()`/`close()`.
453
+
454
+ #### `<web-ui-tooltip>`
455
+
456
+ Tooltip overlay using pointer/focus triggers.
457
+
458
+ | Attribute | Type | Default | Description |
459
+ | ------------------ | ---------------------------------- | ------- | ---------------------------------- |
460
+ | `placement` | `Placement` | `'top'` | Floating UI placement |
461
+ | `content` | `string` | `''` | Tooltip text (alternative to slot) |
462
+ | `open` | `boolean` | `false` | Tooltip visibility |
463
+ | `disabled` | `boolean` | `false` | Disabled state |
464
+ | `show-delay` | `number` | `200` | Show delay in ms |
465
+ | `hide-delay` | `number` | `100` | Hide delay in ms |
466
+ | `offset` | `number` | `6` | Offset from trigger |
467
+ | `portal` | `boolean` | `false` | Render in theme overlay |
468
+ | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
469
+
470
+ **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
471
+
472
+ **Slots:** `default` (trigger), `content` (tooltip panel)
473
+
474
+ `open` is a controlled visibility property. Pointer/focus triggers update it, and direct updates synchronize the local or portal panel. Adjacent tooltips open immediately after the first tooltip is visible; pointer/focus triggers otherwise use delay timers.
475
+
476
+ #### `<web-ui-context-menu>`
477
+
478
+ Right-click context menu.
479
+
480
+ | Attribute | Type | Default | Description |
481
+ | ------------- | --------- | ------- | ------------------------- |
482
+ | `disabled` | `boolean` | `false` | Disabled state |
483
+ | `lock-scroll` | `boolean` | `true` | Prevent background scroll |
484
+
485
+ **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
486
+
487
+ **Slots:** `default` (menu items content)
488
+
489
+ **Methods:** `openAt(x: number, y: number)`, `close()`
490
+
491
+ Opens on `contextmenu` event. Menu items: `<web-ui-dropdown-item>`, `<web-ui-dropdown-divider>`, `<web-ui-dropdown-header>`. Supports keyboard navigation (Arrow keys, Enter, Escape) and submenu hover with `pointerenter`.
492
+
493
+ ---
494
+
495
+ ### Menu
496
+
497
+ #### `<web-ui-dropdown>`
498
+
499
+ Dropdown menu with multi-level submenu support.
500
+
501
+ | Attribute | Type | Default | Description |
502
+ | ------------- | ----------- | ---------------- | -------------------------- |
503
+ | `open` | `boolean` | `false` | Menu visibility |
504
+ | `disabled` | `boolean` | `false` | Disabled state |
505
+ | `placement` | `Placement` | `'bottom-start'` | Floating UI placement |
506
+ | `offset` | `number` | `4` | Offset from trigger |
507
+ | `match-width` | `boolean` | `false` | Match trigger width |
508
+ | `lock-scroll` | `boolean` | `true` | Lock body scroll when open |
509
+
510
+ **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
511
+
512
+ **Slots:** `trigger`, `default` (menu items)
513
+
514
+ **Methods:** `openMenu()`, `closeAll()`
515
+
516
+ Items: `<web-ui-dropdown-item>`, `<web-ui-dropdown-divider>`, `<web-ui-dropdown-header>`. Submenus via `submenu` attribute on `<web-ui-dropdown-item>`. Full keyboard navigation (Arrow keys, Home, End, Enter, Space, Escape).
517
+
518
+ #### `<web-ui-dropdown-item>`
519
+
520
+ Menu item for dropdown or context-menu.
521
+
522
+ | Attribute | Type | Default | Description |
523
+ | ---------- | --------- | ------- | -------------------- |
524
+ | `disabled` | `boolean` | `false` | Disabled state |
525
+ | `submenu` | `boolean` | `false` | Has submenu children |
526
+ | `value` | `string` | `''` | Item value |
527
+ | `pl` | `string` | `''` | Padding-left offset |
528
+
529
+ **Slots:** `prefix`, `default`, `suffix`
530
+
531
+ **Methods:** `focusItem()`
532
+
533
+ Role: `menuitem`. Submenu items should be projected as child elements.
534
+
535
+ #### `<web-ui-dropdown-divider>`
536
+
537
+ Menu divider line. Role: `separator`. No attributes.
538
+
539
+ #### `<web-ui-dropdown-header>`
540
+
541
+ Menu section header. No attributes. Slots: `default` (text content).
542
+
543
+ ---
544
+
545
+ ### Data Display
546
+
547
+ #### `<web-ui-avatar>`
548
+
549
+ Avatar display with image fallback.
550
+
551
+ | Attribute | Type | Default | Description |
552
+ | --------- | ---------------------- | ---------- | -------------------------------- |
553
+ | `src` | `string` | `''` | Image source URL |
554
+ | `alt` | `string` | `''` | Alternative text |
555
+ | `name` | `string` | `''` | Display name (initials fallback) |
556
+ | `size` | `number` | `40` | Avatar size in px |
557
+ | `shape` | `'circle' \| 'square'` | `'circle'` | Shape variant |
558
+
559
+ **Slots:** `default` (fallback content when image fails)
560
+
561
+ #### `<web-ui-badge>`
562
+
563
+ Badge / notification count.
564
+
565
+ | Attribute | Type | Default | Description |
566
+ | ----------- | -------------------------------------------------------------- | ------------- | ------------------------- |
567
+ | `count` | `number` | `0` | Display count |
568
+ | `max` | `number` | `99` | Maximum count (shows 99+) |
569
+ | `dot` | `boolean` | `false` | Dot mode (no count) |
570
+ | `show-zero` | `boolean` | `false` | Show when count is 0 |
571
+ | `hidden` | `boolean` | `false` | Hide completely |
572
+ | `offset-x` | `number` | `0` | Horizontal offset |
573
+ | `offset-y` | `number` | `0` | Vertical offset |
574
+ | `placement` | `'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left'` | `'top-right'` | Badge position |
575
+
576
+ #### `<web-ui-empty>`
577
+
578
+ Empty state placeholder.
579
+
580
+ | Attribute | Type | Default | Description |
581
+ | ------------- | -------------------------------- | ---------- | ---------------- |
582
+ | `title` | `string` | `''` | Title text |
583
+ | `description` | `string` | `''` | Description text |
584
+ | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | Empty state size |
585
+
586
+ **Slots:** `default` (title, overrides `title` prop), `icon`, `description`, `action`
587
+
588
+ #### `<web-ui-icon>`
589
+
590
+ Icon renderer using Iconify data objects.
591
+
592
+ | Property | Type | Default | Description |
593
+ | -------- | ------------- | ------- | -------------------------------- |
594
+ | `.icon` | `IconifyIcon` | — | Icon data (Lit property binding) |
595
+ | `size` | `number` | `18` | Icon size in px |
596
+ | `color` | `string` | — | Icon color |
597
+ | `spin` | `boolean` | `false` | Rotation animation |
598
+
599
+ Has `aria-hidden="true"`.
600
+
601
+ ```js
602
+ import { lucideLoaderCircle } from '@greypan/web-ui/icons'
603
+ html`<web-ui-icon .icon=${lucideLoaderCircle} spin />`
259
604
  ```
260
605
 
261
- ### SVG Draw Lines
606
+ #### `<web-ui-spinner>`
262
607
 
263
- ```vue
264
- <template>
265
- <web-ui-svg-draw-lines>
266
- <svg viewBox="0 0 24 24" width="100" height="100" fill="none" stroke="currentColor" stroke-width="2">
267
- <path d="M4 4v16M8 6h12M8 12h6m-6 6h10" />
268
- </svg>
269
- </web-ui-svg-draw-lines>
270
- </template>
608
+ Loading spinner.
609
+
610
+ | Attribute | Type | Default | Description |
611
+ | ------------- | -------- | ------- | ------------------ |
612
+ | `size` | `number` | `24` | Spinner size in px |
613
+ | `color` | `string` | — | Spinner color |
614
+ | `description` | `string` | `''` | Description text |
615
+
616
+ Role: `status`, `aria-label="加载中"`.
617
+
618
+ **Slots:** `description`
619
+
620
+ **Static API:**
621
+
622
+ ```ts
623
+ import { WebUiSpinner } from '@greypan/web-ui'
624
+
625
+ WebUiSpinner.show() // show
626
+ WebUiSpinner.show({ size: 32, duration: 2000 }) // show with options
627
+ WebUiSpinner.hide() // hide
271
628
  ```
272
629
 
273
- ## API
630
+ `WebUiSpinner.show(options?: { size?: number; duration?: number; description?: string })`
631
+
632
+ ---
274
633
 
275
- ### `<web-ui-button>`
634
+ ### Layout & Utility
276
635
 
277
- Styled button component.
636
+ #### `<web-ui-layout>`
278
637
 
279
- | Attribute | Type | Default | Description |
280
- | --------- | --------- | ------- | ----------------- |
281
- | `full` | `boolean` | `false` | Full width button |
282
- | `primary` | `boolean` | `false` | Primary style |
283
- | `text` | `boolean` | `false` | Text-only style |
284
- | `icon` | `boolean` | `false` | Icon-only mode |
638
+ Page layout grid.
285
639
 
286
- ### `<web-ui-back-top>`
640
+ | Slot | Description |
641
+ | --------- | --------------- |
642
+ | `header` | Sticky top bar |
643
+ | `default` | Main content |
644
+ | `sidebar` | Side navigation |
645
+ | `tabbar` | Bottom tab bar |
646
+
647
+ #### `<web-ui-back-top>`
287
648
 
288
649
  Scroll-to-top button.
289
650
 
290
- | Attribute | Type | Default | Description |
291
- | -------------- | ----------------------- | -------- | ------------------------------- |
292
- | `smooth` | `boolean` | `true` | Smooth scroll animation |
293
- | `threshold` | `number` | `200` | Scroll threshold to show button |
294
- | `visible` | `boolean` | `false` | Current visibility state |
295
- | `scrollTarget` | `HTMLElement \| Window` | `window` | Scroll container |
651
+ | Attribute | Type | Default | Description |
652
+ | -------------- | ----------------------- | -------- | ------------------------ |
653
+ | `smooth` | `boolean` | `true` | Smooth scroll |
654
+ | `threshold` | `number` | `200` | Scroll threshold to show |
655
+ | `visible` | `boolean` | `false` | Current visibility state |
656
+ | `scrollTarget` | `HTMLElement \| Window` | `window` | Scroll container |
296
657
 
297
- **Events:**
658
+ **Slots:** `default` (custom button content)
298
659
 
299
- | Event | Detail | Description |
300
- | ---------------- | ---------------------- | ------------------------ |
301
- | `visible-change` | `{ visible: boolean }` | Visibility state changed |
660
+ **Methods:** `toTop()`
302
661
 
303
- ### `<web-ui-layout>`
662
+ Role: `button`, keyboard Enter scrolls to top.
304
663
 
305
- Page layout with slots.
664
+ #### `<web-ui-svg-draw-lines>`
306
665
 
307
- | Slot | Description |
308
- | --------- | ----------------- |
309
- | `header` | Header area |
310
- | `sidebar` | Sidebar area |
311
- | `tabbar` | Tab bar area |
312
- | (default) | Main content area |
666
+ SVG line drawing animation using `stroke-dashoffset`. Animates geometry in-place — no cloning, no DOM manipulation.
313
667
 
314
- ### `<web-ui-svg-draw-lines>`
668
+ | Attribute | Type | Default | Description |
669
+ | ---------- | -------- | ---------- | ------------------------------------------------- |
670
+ | `duration` | `number` | `1000` | Animation duration in ms, clamped to `[0, 30000]` |
671
+ | `easing` | `string` | `'linear'` | CSS easing function passed to `element.animate()` |
315
672
 
316
- SVG line drawing animation component. Wraps an `<svg>` element and animates stroke-dashoffset.
673
+ Both attributes are reflected.
317
674
 
318
- | Attribute | Type | Default | Description |
319
- | ---------- | -------- | ---------- | ------------------------ |
320
- | `duration` | `number` | `1000` | Animation duration in ms |
321
- | `easing` | `string` | `'linear'` | CSS easing function |
675
+ **Methods:** `replay(): Promise<void>` — cancels running animation, re-collects geometry elements from current DOM, and starts a new animation. All targets animate in parallel with the same duration/easing. Resolves when all complete. Returns immediately without animation when the nearest theme scope uses `motion="reduced"`, or when its `motion="system"` mode matches `prefers-reduced-motion: reduce`.
322
676
 
323
- ```html
324
- <web-ui-svg-draw-lines duration="2000" easing="ease-in-out">
325
- <svg viewBox="0 0 100 100">
326
- <path d="M10 10 L90 10 L90 90 L10 90 Z" fill="none" stroke="black" />
327
- </svg>
328
- </web-ui-svg-draw-lines>
677
+ **Slots:** `default` — SVG content to animate. Accepts inline `<svg>` elements (light DOM) as well as components that render an SVG in an open shadow root, such as `<web-ui-icon>`. Closed shadow roots are skipped.
678
+
679
+ Finds `path`, `rect`, `circle`, `line`, `polyline`, `polygon`, `ellipse` elements by recursively traversing the light DOM and all open shadow roots. Paths ending with `Z`/`z` receive a temporary gap fix for proper closing-segment rendering. After animation completes or is cancelled, all in-line styles are restored.
680
+
681
+ #### `<web-ui-theme>`
682
+
683
+ Theme provider defining CSS custom property tokens.
684
+
685
+ | Attribute | Type | Default | Description |
686
+ | ------------ | --------------------------------- | ---------- | --------------------------------------------- |
687
+ | `appearance` | `'light' \| 'dark' \| 'system'` | `'light'` | Color scheme |
688
+ | `motion` | `'full' \| 'reduced' \| 'system'` | `'system'` | Motion preference for this nested theme scope |
689
+
690
+ **Methods:** `getOverlayRoot()` — returns the portal overlay container
691
+
692
+ Defines `--wui-color-*`, `--wui-shadow-*`, `--wui-layer-*`, and motion tokens. Motion tokens are stable and may be overridden per theme scope: `--wui-duration-press`, `--wui-duration-fast`, `--wui-duration-overlay-enter`, `--wui-duration-overlay-exit`, `--wui-duration-drawer`, `--wui-ease-out`, `--wui-ease-standard`, `--wui-scale-press`, and `--wui-scale-enter`. `motion="system"` follows `prefers-reduced-motion`; use `motion="reduced"` to reduce animation in a scope or `motion="full"` in a nested theme to restore the normal token values. System appearance follows `prefers-color-scheme`.
693
+
694
+ ---
695
+
696
+ ### Notification
697
+
698
+ #### `<web-ui-toast>`
699
+
700
+ Individual toast notification element. Managed by imperative API.
701
+
702
+ **Imperative API:**
703
+
704
+ ```ts
705
+ import { toast } from '@greypan/web-ui'
706
+
707
+ // Create
708
+ toast.success('Operation completed')
709
+ toast.error('Something went wrong', { duration: 5000 })
710
+ toast.info('Heads up')
711
+ toast.warning('Be careful')
712
+
713
+ // With options
714
+ const id = toast({ message: 'Custom', type: 'info', position: 'bottom-right', duration: 4000, closable: true })
715
+
716
+ // Close
717
+ toast.close(id)
718
+ toast.clear()
719
+
720
+ // Update visible content without resetting auto-close timing
721
+ toast.updateMessage(id, { message: 'Upload 60% complete', heading: 'Uploading' })
329
722
  ```
723
+
724
+ **ToastOptions:**
725
+
726
+ | Option | Type | Default | Description |
727
+ | ----------- | ------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------- |
728
+ | `message` | `string` | — | Notification text |
729
+ | `type` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | Toast type |
730
+ | `duration` | `number` | `3000` (`5000` for error) | Auto-close duration (0 = no auto-close) |
731
+ | `closable` | `boolean` | `true` | Show close button |
732
+ | `id` | `string` | auto | Deduplication identifier |
733
+ | `heading` | `string` | `''` | Bold heading text |
734
+ | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | `'top-right'` | Screen position |
735
+ | `target` | `Element` | — | Used to find nearest theme scope |
736
+ | `container` | `HTMLElement` | — | Explicit mount container (highest priority) |
737
+
738
+ **`toast.updateMessage(id, options)`** updates the visible toast's `message` and, when supplied, `heading`. It does not restart the auto-close timer. `options` is `ToastMessageUpdateOptions`: `{ message: string; heading?: string }`.
739
+
740
+ **Events:** `toast-close` (`CustomEvent<{ id: string; reason: 'auto' | 'manual' | 'programmatic' | 'clear' }>`)
741
+
742
+ Hover pauses auto-close timer (uses `pointerenter`/`pointerleave`). Batch-mounts toasts created in the same microtask.
743
+
744
+ ---
745
+
746
+ ### Sub-items
747
+
748
+ #### `<web-ui-option>`
749
+
750
+ Selection option for `<web-ui-select>`.
751
+
752
+ | Attribute | Type | Default | Description |
753
+ | ---------- | --------- | ------- | --------------------------------------------- |
754
+ | `value` | `string` | `''` | Selection value |
755
+ | `label` | `string` | `''` | Display text; falls back to default slot text |
756
+ | `selected` | `boolean` | `false` | Currently selected |
757
+ | `disabled` | `boolean` | `false` | Disabled state |
758
+
759
+ **Slots:** `default` (fallback label text), `prefix` (content before label), `suffix` (content after label)
760
+
761
+ Not form-associated (child of select, not independent submit).
762
+
763
+ #### `<web-ui-segmented-trigger>`
764
+
765
+ Segment trigger for `<web-ui-segmented>`.
766
+
767
+ | Attribute | Type | Default | Description |
768
+ | ---------- | --------- | ------- | ------------------ |
769
+ | `value` | `string` | `''` | Segment value |
770
+ | `checked` | `boolean` | `false` | Currently selected |
771
+ | `disabled` | `boolean` | `false` | Disabled state |
772
+
773
+ **Events:** `change`
774
+
775
+ Not form-associated (child of segmented, not independent submit).