@exegia/corpora-ui 3.0.0 → 3.1.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 (587) hide show
  1. package/README.md +20 -0
  2. package/dist-lib/button-CIRkQXA6.js +152 -0
  3. package/dist-lib/button.js +3 -0
  4. package/dist-lib/card.js +150 -0
  5. package/dist-lib/components/atoms/text/default.d.ts +2 -1143
  6. package/dist-lib/components/blocks/shell/index.d.ts +1 -0
  7. package/dist-lib/components/ui/badge.d.ts +2 -2
  8. package/dist-lib/components/ui/button.d.ts +2 -2
  9. package/dist-lib/components/ui/glasscn/lazy-glass-surface.d.ts +6 -0
  10. package/dist-lib/components/ui/modal.d.ts +10 -0
  11. package/dist-lib/components/ui/overlays.d.ts +4 -0
  12. package/dist-lib/components/ui/select.d.ts +1 -1
  13. package/dist-lib/components/ui/toast.d.ts +2 -0
  14. package/dist-lib/ease-C9At8rnB.js +120 -0
  15. package/dist-lib/exegia-provider-DNTuLljX.js +124 -0
  16. package/dist-lib/index.css +3 -0
  17. package/dist-lib/index.d.ts +3 -0
  18. package/dist-lib/index.js +6482 -8894
  19. package/dist-lib/input.js +28 -0
  20. package/dist-lib/label.js +19 -0
  21. package/dist-lib/lib/segmented-control.d.ts +1 -1
  22. package/dist-lib/lib/state/exegia-provider.d.ts +17 -8
  23. package/dist-lib/lib/state/index.d.ts +2 -0
  24. package/dist-lib/lib/state/portal-context.d.ts +5 -0
  25. package/dist-lib/lib/state/sound-loading.d.ts +4 -0
  26. package/dist-lib/liquid-glass-BihU6nMl.js +50 -0
  27. package/dist-lib/modal-DLaOK-s1.js +82 -0
  28. package/dist-lib/overlays.js +3 -0
  29. package/dist-lib/rolldown-runtime-Dy4uBu1J.js +11 -0
  30. package/dist-lib/scaffold-Qv4hs5Pf.js +925 -0
  31. package/dist-lib/scaffold.js +2 -0
  32. package/dist-lib/shell-BQ9cjIEu.js +722 -0
  33. package/dist-lib/shell.js +2 -0
  34. package/dist-lib/sound-knGH4lj-.js +19 -0
  35. package/dist-lib/state.js +2 -0
  36. package/dist-lib/toast-CataVXHN.js +193 -0
  37. package/dist-lib/utils-BO0qfoA6.js +61 -0
  38. package/package.json +64 -23
  39. package/dist-lib/components/atoms/_layout.d.ts +0 -4
  40. package/dist-lib/components/atoms/bubble/__tests__/bubble.test.d.ts +0 -1
  41. package/dist-lib/components/atoms/bubble/__tests__/reference.test.d.ts +0 -1
  42. package/dist-lib/components/atoms/text/__tests__/text.test.d.ts +0 -1
  43. package/dist-lib/components/atoms/text-selection/__tests__/click-popover.test.d.ts +0 -1
  44. package/dist-lib/components/atoms/text-selection/__tests__/text-selection.test.d.ts +0 -1
  45. package/dist-lib/components/blocks/auth/__tests__/auth-flow-block.test.d.ts +0 -1
  46. package/dist-lib/components/blocks/auth/__tests__/auth-state-atom.test.d.ts +0 -1
  47. package/dist-lib/components/blocks/auth/__tests__/linked-accounts-block.test.d.ts +0 -1
  48. package/dist-lib/components/blocks/auth/__tests__/onboarding-block.test.d.ts +0 -1
  49. package/dist-lib/components/blocks/auth/__tests__/passkey-manager-block.test.d.ts +0 -1
  50. package/dist-lib/components/blocks/auth/__tests__/passkey-sign-in-block.test.d.ts +0 -1
  51. package/dist-lib/components/blocks/auth/__tests__/signup-block.test.d.ts +0 -1
  52. package/dist-lib/components/blocks/auth/__tests__/update-password-block.test.d.ts +0 -1
  53. package/dist-lib/components/blocks/chat/__tests__/ai-panel.test.d.ts +0 -1
  54. package/dist-lib/components/blocks/profile/__tests__/profile-card-block.test.d.ts +0 -1
  55. package/dist-lib/components/blocks/scaffold/__tests__/scaffold-atom.test.d.ts +0 -1
  56. package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +0 -1
  57. package/dist-lib/components/blocks/shell/__tests__/shell-fit-atom.test.d.ts +0 -1
  58. package/dist-lib/components/blocks/shell/__tests__/shell-layout.test.d.ts +0 -1
  59. package/dist-lib/components/blocks/shell/__tests__/shell-metrics.test.d.ts +0 -1
  60. package/dist-lib/components/blocks/sidebar/__tests__/ai-sidebar-atom.test.d.ts +0 -1
  61. package/dist-lib/components/blocks/sidebar/__tests__/ai-sidebar.test.d.ts +0 -1
  62. package/dist-lib/components/blocks/sidebar/__tests__/use-ai-sidebar.test.d.ts +0 -1
  63. package/dist-lib/components/composed/__tests__/logo.test.d.ts +0 -1
  64. package/dist-lib/components/composed/action-bar/__tests__/action-bar.test.d.ts +0 -1
  65. package/dist-lib/components/composed/ai/__tests__/ai.test.d.ts +0 -1
  66. package/dist-lib/components/composed/chat/__tests__/attachment.test.d.ts +0 -1
  67. package/dist-lib/components/composed/chat/__tests__/beautiful-ui.test.d.ts +0 -1
  68. package/dist-lib/components/composed/chat/__tests__/chart.test.d.ts +0 -1
  69. package/dist-lib/components/composed/chat/__tests__/code-block.test.d.ts +0 -1
  70. package/dist-lib/components/composed/chat/__tests__/composer-attachments.test.d.ts +0 -1
  71. package/dist-lib/components/composed/chat/__tests__/composer.test.d.ts +0 -1
  72. package/dist-lib/components/composed/chat/__tests__/flowchart.test.d.ts +0 -1
  73. package/dist-lib/components/composed/chat/__tests__/glass-material.test.d.ts +0 -1
  74. package/dist-lib/components/composed/chat/__tests__/markdown.test.d.ts +0 -1
  75. package/dist-lib/components/composed/chat/__tests__/menu-command.test.d.ts +0 -1
  76. package/dist-lib/components/composed/chat/__tests__/recommendation.test.d.ts +0 -1
  77. package/dist-lib/components/composed/tree/__tests__/tree-atom.test.d.ts +0 -1
  78. package/dist-lib/components/composed/tree/__tests__/tree.test.d.ts +0 -1
  79. package/dist-lib/components/composed/tree/__tests__/use-tree.test.d.ts +0 -1
  80. package/dist-lib/components/composed/user/__tests__/message.test.d.ts +0 -1
  81. package/dist-lib/components/composed/verse/__tests__/verse.test.d.ts +0 -1
  82. package/dist-lib/components/docs/atoms-gallery.d.ts +0 -12
  83. package/dist-lib/components/docs/block-demo-stage.d.ts +0 -7
  84. package/dist-lib/components/docs/blocks-gallery.d.ts +0 -2
  85. package/dist-lib/components/docs/browser.d.ts +0 -10
  86. package/dist-lib/components/docs/code-block.d.ts +0 -4
  87. package/dist-lib/components/docs/components-gallery.d.ts +0 -2
  88. package/dist-lib/components/docs/demo-controls.d.ts +0 -21
  89. package/dist-lib/components/docs/hero.d.ts +0 -5
  90. package/dist-lib/components/docs/mermaid.d.ts +0 -3
  91. package/dist-lib/components/icons/__tests__/icons.test.d.ts +0 -1
  92. package/dist-lib/components/stories/attachment.story.d.ts +0 -11
  93. package/dist-lib/components/stories/auth-flow-block.story.d.ts +0 -2
  94. package/dist-lib/components/stories/avatar.story.d.ts +0 -6
  95. package/dist-lib/components/stories/base.story.d.ts +0 -6
  96. package/dist-lib/components/stories/bubble.story.d.ts +0 -6
  97. package/dist-lib/components/stories/button.story.d.ts +0 -9
  98. package/dist-lib/components/stories/card.story.d.ts +0 -8
  99. package/dist-lib/components/stories/chart.story.d.ts +0 -71
  100. package/dist-lib/components/stories/checkbox.story.d.ts +0 -6
  101. package/dist-lib/components/stories/code-auth-block.story.d.ts +0 -2
  102. package/dist-lib/components/stories/code-block.story.d.ts +0 -12
  103. package/dist-lib/components/stories/composed-password-input.story.d.ts +0 -6
  104. package/dist-lib/components/stories/context-cards.story.d.ts +0 -3
  105. package/dist-lib/components/stories/emoji-action-bar.story.d.ts +0 -3
  106. package/dist-lib/components/stories/field.story.d.ts +0 -6
  107. package/dist-lib/components/stories/file-icons.story.d.ts +0 -5
  108. package/dist-lib/components/stories/filter-table.story.d.ts +0 -21
  109. package/dist-lib/components/stories/flowchart.story.d.ts +0 -3
  110. package/dist-lib/components/stories/forgot-password-block.story.d.ts +0 -2
  111. package/dist-lib/components/stories/frame.story.d.ts +0 -8
  112. package/dist-lib/components/stories/index.d.ts +0 -54
  113. package/dist-lib/components/stories/input-group.story.d.ts +0 -6
  114. package/dist-lib/components/stories/input.story.d.ts +0 -8
  115. package/dist-lib/components/stories/insight-cards.story.d.ts +0 -74
  116. package/dist-lib/components/stories/label.story.d.ts +0 -8
  117. package/dist-lib/components/stories/linked-accounts-block.story.d.ts +0 -2
  118. package/dist-lib/components/stories/login-block.story.d.ts +0 -2
  119. package/dist-lib/components/stories/logo.story.d.ts +0 -3
  120. package/dist-lib/components/stories/markdown.story.d.ts +0 -7
  121. package/dist-lib/components/stories/menu-command.story.d.ts +0 -13
  122. package/dist-lib/components/stories/onboarding-block.story.d.ts +0 -2
  123. package/dist-lib/components/stories/otp-field.story.d.ts +0 -8
  124. package/dist-lib/components/stories/passkey-manager-block.story.d.ts +0 -2
  125. package/dist-lib/components/stories/passkey-sign-in-block.story.d.ts +0 -2
  126. package/dist-lib/components/stories/password-input.story.d.ts +0 -9
  127. package/dist-lib/components/stories/profile-card-block.story.d.ts +0 -6
  128. package/dist-lib/components/stories/recommendation-card.story.d.ts +0 -3
  129. package/dist-lib/components/stories/records-table.story.d.ts +0 -23
  130. package/dist-lib/components/stories/reference.story.d.ts +0 -9
  131. package/dist-lib/components/stories/research-answer.story.d.ts +0 -10
  132. package/dist-lib/components/stories/scaffold-root.story.d.ts +0 -6
  133. package/dist-lib/components/stories/separator.story.d.ts +0 -6
  134. package/dist-lib/components/stories/shell-layout.story.d.ts +0 -2
  135. package/dist-lib/components/stories/signup-block.story.d.ts +0 -2
  136. package/dist-lib/components/stories/social-providers.story.d.ts +0 -3
  137. package/dist-lib/components/stories/streaming-text.story.d.ts +0 -3
  138. package/dist-lib/components/stories/text.story.d.ts +0 -8
  139. package/dist-lib/components/stories/textarea.story.d.ts +0 -6
  140. package/dist-lib/components/stories/tree.story.d.ts +0 -4
  141. package/dist-lib/components/stories/update-password-block.story.d.ts +0 -2
  142. package/dist-lib/components/stories/user.story.d.ts +0 -8
  143. package/dist-lib/components/stories/verse.story.d.ts +0 -6
  144. package/dist-lib/components/ui/chat/__tests__/segmented-toggle.test.d.ts +0 -1
  145. package/dist-lib/index.js.map +0 -1
  146. package/dist-lib/lib/state/__tests__/sound.test.d.ts +0 -1
  147. package/dist-lib/lib/utils.test.d.ts +0 -1
  148. package/src/assets/css/base.css +0 -62
  149. package/src/assets/css/chat.css +0 -229
  150. package/src/assets/css/shadows.css +0 -157
  151. package/src/assets/css/theme.css +0 -321
  152. package/src/components/AGENTS.md +0 -0
  153. package/src/components/atoms/_layout.tsx +0 -9
  154. package/src/components/atoms/avatar/base.tsx +0 -70
  155. package/src/components/atoms/avatar/index.ts +0 -10
  156. package/src/components/atoms/avatar/types.ts +0 -24
  157. package/src/components/atoms/avatar/utils.ts +0 -54
  158. package/src/components/atoms/avatar/verified.tsx +0 -16
  159. package/src/components/atoms/background/index.ts +0 -8
  160. package/src/components/atoms/background/texture.tsx +0 -29
  161. package/src/components/atoms/background/type.ts +0 -10
  162. package/src/components/atoms/background/utils.ts +0 -13
  163. package/src/components/atoms/bubble/__tests__/bubble.test.tsx +0 -193
  164. package/src/components/atoms/bubble/__tests__/reference.test.tsx +0 -25
  165. package/src/components/atoms/bubble/actions.tsx +0 -28
  166. package/src/components/atoms/bubble/context.ts +0 -11
  167. package/src/components/atoms/bubble/default.tsx +0 -46
  168. package/src/components/atoms/bubble/header.tsx +0 -36
  169. package/src/components/atoms/bubble/index.ts +0 -37
  170. package/src/components/atoms/bubble/message.tsx +0 -32
  171. package/src/components/atoms/bubble/reactions.tsx +0 -191
  172. package/src/components/atoms/bubble/types.ts +0 -82
  173. package/src/components/atoms/bubble/utils.ts +0 -46
  174. package/src/components/atoms/chip.tsx +0 -61
  175. package/src/components/atoms/field.tsx +0 -43
  176. package/src/components/atoms/index.ts +0 -26
  177. package/src/components/atoms/loader.tsx +0 -33
  178. package/src/components/atoms/reference.tsx +0 -41
  179. package/src/components/atoms/text/__tests__/text.test.tsx +0 -47
  180. package/src/components/atoms/text/default.tsx +0 -59
  181. package/src/components/atoms/text/heading.tsx +0 -58
  182. package/src/components/atoms/text/index.ts +0 -28
  183. package/src/components/atoms/text/label.tsx +0 -16
  184. package/src/components/atoms/text/paragraph.tsx +0 -55
  185. package/src/components/atoms/text/span.tsx +0 -14
  186. package/src/components/atoms/text/types.ts +0 -41
  187. package/src/components/atoms/text/utils.ts +0 -16
  188. package/src/components/atoms/text-selection/__tests__/click-popover.test.tsx +0 -61
  189. package/src/components/atoms/text-selection/__tests__/text-selection.test.tsx +0 -90
  190. package/src/components/atoms/text-selection/click-popover.tsx +0 -62
  191. package/src/components/atoms/text-selection/index.ts +0 -30
  192. package/src/components/atoms/text-selection/popover.tsx +0 -103
  193. package/src/components/atoms/text-selection/selection-atom.ts +0 -51
  194. package/src/components/atoms/text-selection/selection.tsx +0 -56
  195. package/src/components/atoms/text-selection/types.ts +0 -125
  196. package/src/components/atoms/text-selection/use-selection.ts +0 -84
  197. package/src/components/atoms/types.ts +0 -46
  198. package/src/components/blocks/.gitkeep +0 -0
  199. package/src/components/blocks/auth/__tests__/auth-flow-block.test.tsx +0 -183
  200. package/src/components/blocks/auth/__tests__/auth-state-atom.test.tsx +0 -247
  201. package/src/components/blocks/auth/__tests__/linked-accounts-block.test.tsx +0 -128
  202. package/src/components/blocks/auth/__tests__/onboarding-block.test.tsx +0 -183
  203. package/src/components/blocks/auth/__tests__/passkey-manager-block.test.tsx +0 -131
  204. package/src/components/blocks/auth/__tests__/passkey-sign-in-block.test.tsx +0 -59
  205. package/src/components/blocks/auth/__tests__/signup-block.test.tsx +0 -176
  206. package/src/components/blocks/auth/__tests__/update-password-block.test.tsx +0 -95
  207. package/src/components/blocks/auth/auth-flow-atom.ts +0 -238
  208. package/src/components/blocks/auth/auth-flow-block.tsx +0 -211
  209. package/src/components/blocks/auth/auth-session-atom.ts +0 -82
  210. package/src/components/blocks/auth/auth-shell.tsx +0 -265
  211. package/src/components/blocks/auth/auth-state.ts +0 -40
  212. package/src/components/blocks/auth/code-auth-block.tsx +0 -176
  213. package/src/components/blocks/auth/forgot-password-block.tsx +0 -112
  214. package/src/components/blocks/auth/linked-accounts-block.tsx +0 -203
  215. package/src/components/blocks/auth/login-block.tsx +0 -217
  216. package/src/components/blocks/auth/onboarding/profile-step.tsx +0 -216
  217. package/src/components/blocks/auth/onboarding-block.tsx +0 -183
  218. package/src/components/blocks/auth/passkey-manager-block.tsx +0 -316
  219. package/src/components/blocks/auth/passkey-sign-in-block.tsx +0 -67
  220. package/src/components/blocks/auth/signup-block.tsx +0 -256
  221. package/src/components/blocks/auth/type.ts +0 -436
  222. package/src/components/blocks/auth/update-password-block.tsx +0 -139
  223. package/src/components/blocks/auth/use-auth-state.ts +0 -127
  224. package/src/components/blocks/chat/__tests__/ai-panel.test.tsx +0 -65
  225. package/src/components/blocks/chat/banners.tsx +0 -61
  226. package/src/components/blocks/chat/base.tsx +0 -84
  227. package/src/components/blocks/chat/header.tsx +0 -60
  228. package/src/components/blocks/chat/index.ts +0 -37
  229. package/src/components/blocks/chat/recommendation-stack.tsx +0 -41
  230. package/src/components/blocks/chat/shared.tsx +0 -27
  231. package/src/components/blocks/chat/suggested-prompts.tsx +0 -45
  232. package/src/components/blocks/chat/type.ts +0 -96
  233. package/src/components/blocks/chat/version-history-record.tsx +0 -37
  234. package/src/components/blocks/layout.ts +0 -35
  235. package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +0 -362
  236. package/src/components/blocks/profile/index.ts +0 -28
  237. package/src/components/blocks/profile/profile-card-atom.ts +0 -247
  238. package/src/components/blocks/profile/profile-card-block.tsx +0 -369
  239. package/src/components/blocks/profile/type.ts +0 -93
  240. package/src/components/blocks/profile/use-profile-card-state.ts +0 -67
  241. package/src/components/blocks/profile/use-profile-card.ts +0 -132
  242. package/src/components/blocks/scaffold/__tests__/scaffold-atom.test.tsx +0 -236
  243. package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +0 -524
  244. package/src/components/blocks/scaffold/constants.ts +0 -39
  245. package/src/components/blocks/scaffold/index.ts +0 -60
  246. package/src/components/blocks/scaffold/panel-close-button.tsx +0 -36
  247. package/src/components/blocks/scaffold/panel-menu-button.tsx +0 -221
  248. package/src/components/blocks/scaffold/scaffold-actions.tsx +0 -92
  249. package/src/components/blocks/scaffold/scaffold-atom.ts +0 -427
  250. package/src/components/blocks/scaffold/scaffold-canvas.tsx +0 -162
  251. package/src/components/blocks/scaffold/scaffold-context.ts +0 -20
  252. package/src/components/blocks/scaffold/scaffold-inspector.tsx +0 -137
  253. package/src/components/blocks/scaffold/scaffold-main.tsx +0 -28
  254. package/src/components/blocks/scaffold/scaffold-panel.tsx +0 -186
  255. package/src/components/blocks/scaffold/scaffold-root.tsx +0 -120
  256. package/src/components/blocks/scaffold/scaffold-sidebar.tsx +0 -31
  257. package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +0 -122
  258. package/src/components/blocks/scaffold/scaffold-tab.tsx +0 -108
  259. package/src/components/blocks/scaffold/type.ts +0 -264
  260. package/src/components/blocks/scaffold/use-scaffold-state.ts +0 -85
  261. package/src/components/blocks/scaffold/use-scaffold.ts +0 -59
  262. package/src/components/blocks/scaffold/utils.ts +0 -112
  263. package/src/components/blocks/shell/__tests__/shell-fit-atom.test.tsx +0 -362
  264. package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +0 -596
  265. package/src/components/blocks/shell/__tests__/shell-metrics.test.ts +0 -154
  266. package/src/components/blocks/shell/animated-panel-inset.tsx +0 -27
  267. package/src/components/blocks/shell/animated-panel-provider.tsx +0 -269
  268. package/src/components/blocks/shell/animated-panel-trigger.tsx +0 -51
  269. package/src/components/blocks/shell/animated-panel.tsx +0 -180
  270. package/src/components/blocks/shell/index.ts +0 -30
  271. package/src/components/blocks/shell/shell-fit-atom.ts +0 -246
  272. package/src/components/blocks/shell/shell-layout.tsx +0 -139
  273. package/src/components/blocks/shell/shell-metrics.ts +0 -133
  274. package/src/components/blocks/shell/shell-panel-atom.ts +0 -74
  275. package/src/components/blocks/shell/type.ts +0 -361
  276. package/src/components/blocks/shell/use-shell-fit-state.ts +0 -49
  277. package/src/components/blocks/shell/use-shell-fit.ts +0 -153
  278. package/src/components/blocks/shell/use-shell-panels.ts +0 -126
  279. package/src/components/blocks/shell/utils.ts +0 -187
  280. package/src/components/blocks/sidebar/__tests__/ai-sidebar-atom.test.tsx +0 -325
  281. package/src/components/blocks/sidebar/__tests__/ai-sidebar.test.tsx +0 -93
  282. package/src/components/blocks/sidebar/__tests__/use-ai-sidebar.test.tsx +0 -310
  283. package/src/components/blocks/sidebar/ai-sidebar-atom.ts +0 -698
  284. package/src/components/blocks/sidebar/ai-sidebar.tsx +0 -150
  285. package/src/components/blocks/sidebar/index.ts +0 -67
  286. package/src/components/blocks/sidebar/marquee-label.tsx +0 -59
  287. package/src/components/blocks/sidebar/sidebar-context.ts +0 -15
  288. package/src/components/blocks/sidebar/sidebar-icon.tsx +0 -14
  289. package/src/components/blocks/sidebar/sidebar-row.tsx +0 -378
  290. package/src/components/blocks/sidebar/type.ts +0 -294
  291. package/src/components/blocks/sidebar/use-ai-sidebar-state.ts +0 -123
  292. package/src/components/blocks/sidebar/use-ai-sidebar.ts +0 -619
  293. package/src/components/blocks/sidebar/utils.ts +0 -170
  294. package/src/components/blocks/types.ts +0 -6
  295. package/src/components/composed/.gitkeep +0 -0
  296. package/src/components/composed/__tests__/logo.test.tsx +0 -60
  297. package/src/components/composed/action-bar/__tests__/action-bar.test.tsx +0 -125
  298. package/src/components/composed/action-bar/action.tsx +0 -77
  299. package/src/components/composed/action-bar/emojis.tsx +0 -97
  300. package/src/components/composed/action-bar/index.ts +0 -24
  301. package/src/components/composed/action-bar/toolbar.tsx +0 -66
  302. package/src/components/composed/action-bar/types.ts +0 -58
  303. package/src/components/composed/action-bar/utils.ts +0 -140
  304. package/src/components/composed/ai/__tests__/ai.test.tsx +0 -66
  305. package/src/components/composed/ai/avatar.tsx +0 -19
  306. package/src/components/composed/ai/index.ts +0 -36
  307. package/src/components/composed/ai/message.tsx +0 -49
  308. package/src/components/composed/ai/research-answer.tsx +0 -157
  309. package/src/components/composed/ai/shared.tsx +0 -94
  310. package/src/components/composed/ai/streaming-text.tsx +0 -94
  311. package/src/components/composed/ai/suggested-prompt.tsx +0 -183
  312. package/src/components/composed/ai/types.ts +0 -81
  313. package/src/components/composed/chat/__tests__/attachment.test.tsx +0 -218
  314. package/src/components/composed/chat/__tests__/beautiful-ui.test.tsx +0 -57
  315. package/src/components/composed/chat/__tests__/chart.test.tsx +0 -316
  316. package/src/components/composed/chat/__tests__/code-block.test.tsx +0 -12
  317. package/src/components/composed/chat/__tests__/composer-attachments.test.tsx +0 -101
  318. package/src/components/composed/chat/__tests__/composer.test.tsx +0 -248
  319. package/src/components/composed/chat/__tests__/flowchart.test.tsx +0 -145
  320. package/src/components/composed/chat/__tests__/glass-material.test.tsx +0 -54
  321. package/src/components/composed/chat/__tests__/markdown.test.tsx +0 -25
  322. package/src/components/composed/chat/__tests__/menu-command.test.tsx +0 -35
  323. package/src/components/composed/chat/__tests__/recommendation.test.tsx +0 -43
  324. package/src/components/composed/chat/actions.tsx +0 -81
  325. package/src/components/composed/chat/attachment-file.ts +0 -82
  326. package/src/components/composed/chat/attachment.tsx +0 -498
  327. package/src/components/composed/chat/chart/chart-atom.ts +0 -55
  328. package/src/components/composed/chat/chart/chart-context.ts +0 -9
  329. package/src/components/composed/chat/chart/chart-legend.tsx +0 -75
  330. package/src/components/composed/chat/chart/chart-plot.tsx +0 -233
  331. package/src/components/composed/chat/chart/chart-root.tsx +0 -229
  332. package/src/components/composed/chat/chart/chart-tooltip.tsx +0 -74
  333. package/src/components/composed/chat/chart/constants.ts +0 -8
  334. package/src/components/composed/chat/chart/evilcharts/README.md +0 -8
  335. package/src/components/composed/chat/chart/evilcharts/echarts-area-chart.tsx +0 -2358
  336. package/src/components/composed/chat/chart/evilcharts/echarts-bar-chart.tsx +0 -2261
  337. package/src/components/composed/chat/chart/evilcharts/echarts-brush.tsx +0 -233
  338. package/src/components/composed/chat/chart/evilcharts/echarts-chart.tsx +0 -214
  339. package/src/components/composed/chat/chart/evilcharts/echarts-dot.tsx +0 -111
  340. package/src/components/composed/chat/chart/evilcharts/echarts-legend.tsx +0 -131
  341. package/src/components/composed/chat/chart/evilcharts/echarts-line-chart.tsx +0 -2112
  342. package/src/components/composed/chat/chart/evilcharts/echarts-pie-chart.tsx +0 -1235
  343. package/src/components/composed/chat/chart/evilcharts/echarts-tooltip.tsx +0 -125
  344. package/src/components/composed/chat/chart/index.ts +0 -13
  345. package/src/components/composed/chat/chart/type.ts +0 -88
  346. package/src/components/composed/chat/chart/use-chart-state.ts +0 -22
  347. package/src/components/composed/chat/chart/use-chart.ts +0 -70
  348. package/src/components/composed/chat/chart/utils.ts +0 -47
  349. package/src/components/composed/chat/chart.tsx +0 -3
  350. package/src/components/composed/chat/code-block.tsx +0 -123
  351. package/src/components/composed/chat/composer/add-button.tsx +0 -29
  352. package/src/components/composed/chat/composer/base.tsx +0 -573
  353. package/src/components/composed/chat/composer/command-menu.tsx +0 -47
  354. package/src/components/composed/chat/composer/hooks.ts +0 -29
  355. package/src/components/composed/chat/composer/index.ts +0 -19
  356. package/src/components/composed/chat/composer/send-button.tsx +0 -36
  357. package/src/components/composed/chat/composer/suggestions.tsx +0 -115
  358. package/src/components/composed/chat/composer/use-dictation.ts +0 -123
  359. package/src/components/composed/chat/composer/utils.ts +0 -74
  360. package/src/components/composed/chat/context-cards.tsx +0 -63
  361. package/src/components/composed/chat/filter-table.tsx +0 -119
  362. package/src/components/composed/chat/flowchart/chart-node.tsx +0 -162
  363. package/src/components/composed/chat/flowchart/connector.tsx +0 -32
  364. package/src/components/composed/chat/flowchart/constant.ts +0 -26
  365. package/src/components/composed/chat/flowchart/default.tsx +0 -230
  366. package/src/components/composed/chat/flowchart/edge-toolbar.tsx +0 -73
  367. package/src/components/composed/chat/flowchart/hooks.tsx +0 -347
  368. package/src/components/composed/chat/flowchart/index.ts +0 -14
  369. package/src/components/composed/chat/flowchart/step-body.tsx +0 -82
  370. package/src/components/composed/chat/flowchart/types.ts +0 -89
  371. package/src/components/composed/chat/flowchart/utils.ts +0 -78
  372. package/src/components/composed/chat/hint.tsx +0 -59
  373. package/src/components/composed/chat/index.ts +0 -92
  374. package/src/components/composed/chat/insight-cards.tsx +0 -199
  375. package/src/components/composed/chat/markdown-atom.ts +0 -15
  376. package/src/components/composed/chat/markdown.tsx +0 -213
  377. package/src/components/composed/chat/recommendation/checkbox.tsx +0 -122
  378. package/src/components/composed/chat/recommendation/constant.ts +0 -54
  379. package/src/components/composed/chat/recommendation/default.tsx +0 -25
  380. package/src/components/composed/chat/recommendation/group.tsx +0 -36
  381. package/src/components/composed/chat/recommendation/index.ts +0 -17
  382. package/src/components/composed/chat/recommendation/item.tsx +0 -215
  383. package/src/components/composed/chat/recommendation/types.ts +0 -76
  384. package/src/components/composed/chat/records-table.tsx +0 -130
  385. package/src/components/composed/chat/type.ts +0 -157
  386. package/src/components/composed/logo.tsx +0 -171
  387. package/src/components/composed/password-input.tsx +0 -179
  388. package/src/components/composed/reader/applied-mark.tsx +0 -32
  389. package/src/components/composed/reader/apply-toast.tsx +0 -49
  390. package/src/components/composed/reader/index.ts +0 -10
  391. package/src/components/composed/reader/selection-highlight.tsx +0 -26
  392. package/src/components/composed/reader/selection-popover.tsx +0 -149
  393. package/src/components/composed/reader/type.ts +0 -48
  394. package/src/components/composed/social-providers.tsx +0 -154
  395. package/src/components/composed/tree/CLAUDE.md +0 -145
  396. package/src/components/composed/tree/__tests__/tree-atom.test.tsx +0 -217
  397. package/src/components/composed/tree/__tests__/tree.test.tsx +0 -522
  398. package/src/components/composed/tree/__tests__/use-tree.test.tsx +0 -333
  399. package/src/components/composed/tree/constants.ts +0 -55
  400. package/src/components/composed/tree/index.ts +0 -51
  401. package/src/components/composed/tree/tree-atom.ts +0 -590
  402. package/src/components/composed/tree/tree-context.ts +0 -13
  403. package/src/components/composed/tree/tree-node.tsx +0 -496
  404. package/src/components/composed/tree/tree.tsx +0 -233
  405. package/src/components/composed/tree/type.ts +0 -322
  406. package/src/components/composed/tree/use-tree-dnd.ts +0 -179
  407. package/src/components/composed/tree/use-tree-state.ts +0 -105
  408. package/src/components/composed/tree/use-tree.ts +0 -311
  409. package/src/components/composed/tree/utils.ts +0 -172
  410. package/src/components/composed/types.ts +0 -9
  411. package/src/components/composed/user/__tests__/message.test.tsx +0 -79
  412. package/src/components/composed/user/index.ts +0 -14
  413. package/src/components/composed/user/info.tsx +0 -56
  414. package/src/components/composed/user/message.tsx +0 -36
  415. package/src/components/composed/user/pill.tsx +0 -20
  416. package/src/components/composed/user/types.ts +0 -47
  417. package/src/components/composed/user/utils.ts +0 -0
  418. package/src/components/composed/verse/__tests__/verse.test.tsx +0 -57
  419. package/src/components/composed/verse/index.ts +0 -2
  420. package/src/components/composed/verse/types.ts +0 -24
  421. package/src/components/composed/verse/verse.tsx +0 -100
  422. package/src/components/docs/atoms-gallery.tsx +0 -286
  423. package/src/components/docs/block-demo-stage.tsx +0 -38
  424. package/src/components/docs/blocks-gallery.tsx +0 -224
  425. package/src/components/docs/browser.tsx +0 -87
  426. package/src/components/docs/code-block.tsx +0 -10
  427. package/src/components/docs/components-gallery.tsx +0 -182
  428. package/src/components/docs/demo-controls.tsx +0 -142
  429. package/src/components/docs/hero.tsx +0 -197
  430. package/src/components/docs/mermaid.tsx +0 -55
  431. package/src/components/icons/README.md +0 -91
  432. package/src/components/icons/__tests__/icons.test.tsx +0 -58
  433. package/src/components/icons/file-badge-cfm.tsx +0 -615
  434. package/src/components/icons/file-badge-corpus.tsx +0 -604
  435. package/src/components/icons/file-badge-pdf.tsx +0 -570
  436. package/src/components/icons/file-badge-tei.tsx +0 -524
  437. package/src/components/icons/file-badge-tf.tsx +0 -620
  438. package/src/components/icons/file-badge-txt.tsx +0 -607
  439. package/src/components/icons/file-badge-xml.tsx +0 -576
  440. package/src/components/icons/file-wordmark-cfm.tsx +0 -424
  441. package/src/components/icons/file-wordmark-corpus.tsx +0 -454
  442. package/src/components/icons/file-wordmark-pdf.tsx +0 -470
  443. package/src/components/icons/file-wordmark-tei.tsx +0 -424
  444. package/src/components/icons/file-wordmark-tf.tsx +0 -406
  445. package/src/components/icons/file-wordmark-txt.tsx +0 -421
  446. package/src/components/icons/file-wordmark-xml.tsx +0 -424
  447. package/src/components/icons/index.ts +0 -16
  448. package/src/components/icons/types.ts +0 -8
  449. package/src/components/motion/index.ts +0 -0
  450. package/src/components/motion/overflow-actions.tsx +0 -376
  451. package/src/components/motion/popover-morph.tsx +0 -292
  452. package/src/components/motion/popover-position.ts +0 -86
  453. package/src/components/motion/shared-layout-bg.tsx +0 -151
  454. package/src/components/stories/attachment.story.tsx +0 -40
  455. package/src/components/stories/auth-flow-block.story.tsx +0 -17
  456. package/src/components/stories/avatar.story.tsx +0 -33
  457. package/src/components/stories/base.story.tsx +0 -70
  458. package/src/components/stories/bubble.story.tsx +0 -61
  459. package/src/components/stories/button.story.tsx +0 -32
  460. package/src/components/stories/card.story.tsx +0 -36
  461. package/src/components/stories/chart.story.tsx +0 -69
  462. package/src/components/stories/checkbox.story.tsx +0 -30
  463. package/src/components/stories/code-auth-block.story.tsx +0 -20
  464. package/src/components/stories/code-block.story.tsx +0 -35
  465. package/src/components/stories/composed-password-input.story.tsx +0 -41
  466. package/src/components/stories/context-cards.story.tsx +0 -30
  467. package/src/components/stories/emoji-action-bar.story.tsx +0 -20
  468. package/src/components/stories/field.story.tsx +0 -32
  469. package/src/components/stories/file-icons.story.tsx +0 -20
  470. package/src/components/stories/filter-table.story.tsx +0 -63
  471. package/src/components/stories/flowchart.story.tsx +0 -25
  472. package/src/components/stories/forgot-password-block.story.tsx +0 -14
  473. package/src/components/stories/frame.story.tsx +0 -32
  474. package/src/components/stories/index.ts +0 -57
  475. package/src/components/stories/input-group.story.tsx +0 -27
  476. package/src/components/stories/input.story.tsx +0 -42
  477. package/src/components/stories/insight-cards.story.tsx +0 -67
  478. package/src/components/stories/label.story.tsx +0 -32
  479. package/src/components/stories/linked-accounts-block.story.tsx +0 -23
  480. package/src/components/stories/login-block.story.tsx +0 -18
  481. package/src/components/stories/logo.story.tsx +0 -21
  482. package/src/components/stories/markdown.story.tsx +0 -31
  483. package/src/components/stories/menu-command.story.tsx +0 -43
  484. package/src/components/stories/onboarding-block.story.tsx +0 -14
  485. package/src/components/stories/otp-field.story.tsx +0 -35
  486. package/src/components/stories/passkey-manager-block.story.tsx +0 -18
  487. package/src/components/stories/passkey-sign-in-block.story.tsx +0 -15
  488. package/src/components/stories/password-input.story.tsx +0 -35
  489. package/src/components/stories/profile-card-block.story.tsx +0 -29
  490. package/src/components/stories/recommendation-card.story.tsx +0 -37
  491. package/src/components/stories/records-table.story.tsx +0 -52
  492. package/src/components/stories/reference.story.tsx +0 -33
  493. package/src/components/stories/research-answer.story.tsx +0 -32
  494. package/src/components/stories/scaffold-root.story.tsx +0 -47
  495. package/src/components/stories/separator.story.tsx +0 -37
  496. package/src/components/stories/shell-layout.story.tsx +0 -19
  497. package/src/components/stories/signup-block.story.tsx +0 -18
  498. package/src/components/stories/social-providers.story.tsx +0 -26
  499. package/src/components/stories/streaming-text.story.tsx +0 -25
  500. package/src/components/stories/text.story.tsx +0 -31
  501. package/src/components/stories/textarea.story.tsx +0 -34
  502. package/src/components/stories/tree.story.tsx +0 -31
  503. package/src/components/stories/update-password-block.story.tsx +0 -14
  504. package/src/components/stories/user.story.tsx +0 -32
  505. package/src/components/stories/verse.story.tsx +0 -42
  506. package/src/components/theme-provider.tsx +0 -230
  507. package/src/components/types.ts +0 -106
  508. package/src/components/ui/accordion.tsx +0 -68
  509. package/src/components/ui/alert-dialog.tsx +0 -167
  510. package/src/components/ui/alert.tsx +0 -84
  511. package/src/components/ui/autocomplete.tsx +0 -303
  512. package/src/components/ui/avatar.tsx +0 -109
  513. package/src/components/ui/badge.tsx +0 -101
  514. package/src/components/ui/button-group.tsx +0 -88
  515. package/src/components/ui/button.tsx +0 -192
  516. package/src/components/ui/card.tsx +0 -253
  517. package/src/components/ui/chat/__tests__/segmented-toggle.test.tsx +0 -27
  518. package/src/components/ui/chat/avatar-handle.tsx +0 -30
  519. package/src/components/ui/chat/badges.tsx +0 -85
  520. package/src/components/ui/chat/buttons.tsx +0 -94
  521. package/src/components/ui/chat/chips.tsx +0 -106
  522. package/src/components/ui/chat/dot.tsx +0 -81
  523. package/src/components/ui/chat/icon-button.tsx +0 -43
  524. package/src/components/ui/chat/icon-tile.tsx +0 -33
  525. package/src/components/ui/chat/index.ts +0 -12
  526. package/src/components/ui/chat/inline-source.tsx +0 -94
  527. package/src/components/ui/chat/segmented-toggle.tsx +0 -99
  528. package/src/components/ui/chat/tag.tsx +0 -61
  529. package/src/components/ui/chat/thumbnail.tsx +0 -31
  530. package/src/components/ui/chat/waveform.tsx +0 -30
  531. package/src/components/ui/checkbox.tsx +0 -83
  532. package/src/components/ui/collapsible.tsx +0 -42
  533. package/src/components/ui/command.tsx +0 -263
  534. package/src/components/ui/context-menu.tsx +0 -342
  535. package/src/components/ui/emoji-picker.tsx +0 -161
  536. package/src/components/ui/field.tsx +0 -89
  537. package/src/components/ui/frame.tsx +0 -87
  538. package/src/components/ui/glasscn/glass-button-group.tsx +0 -34
  539. package/src/components/ui/glasscn/glass-button.tsx +0 -25
  540. package/src/components/ui/glasscn/glass-container.tsx +0 -94
  541. package/src/components/ui/glasscn/glass-separator.tsx +0 -39
  542. package/src/components/ui/glasscn/liquid-glass.tsx +0 -59
  543. package/src/components/ui/group.tsx +0 -92
  544. package/src/components/ui/input-group.tsx +0 -105
  545. package/src/components/ui/input.tsx +0 -70
  546. package/src/components/ui/kbd.tsx +0 -32
  547. package/src/components/ui/label.tsx +0 -36
  548. package/src/components/ui/menu-command.tsx +0 -156
  549. package/src/components/ui/menu.tsx +0 -352
  550. package/src/components/ui/otp-field.tsx +0 -88
  551. package/src/components/ui/popover-glass.tsx +0 -158
  552. package/src/components/ui/popover.tsx +0 -120
  553. package/src/components/ui/preview-card.tsx +0 -61
  554. package/src/components/ui/scroll-area.tsx +0 -80
  555. package/src/components/ui/select.tsx +0 -257
  556. package/src/components/ui/separator.tsx +0 -23
  557. package/src/components/ui/sheet.tsx +0 -135
  558. package/src/components/ui/skeleton.tsx +0 -18
  559. package/src/components/ui/spinner.tsx +0 -17
  560. package/src/components/ui/switch.tsx +0 -30
  561. package/src/components/ui/tabs.tsx +0 -120
  562. package/src/components/ui/textarea.tsx +0 -58
  563. package/src/components/ui/toast.tsx +0 -325
  564. package/src/components/ui/toolbar.tsx +0 -92
  565. package/src/components/ui/tooltip.tsx +0 -67
  566. package/src/index.css +0 -10
  567. package/src/index.ts +0 -102
  568. package/src/lib/attachment-content.ts +0 -28
  569. package/src/lib/auth-accent.ts +0 -49
  570. package/src/lib/ease.ts +0 -90
  571. package/src/lib/glass-variants.ts +0 -22
  572. package/src/lib/hooks/use-hover-capable.ts +0 -23
  573. package/src/lib/hooks/use-mobile.ts +0 -21
  574. package/src/lib/keyed-atom.ts +0 -60
  575. package/src/lib/segmented-control.ts +0 -42
  576. package/src/lib/sound.ts +0 -39
  577. package/src/lib/specular.ts +0 -10
  578. package/src/lib/state/__tests__/sound.test.tsx +0 -53
  579. package/src/lib/state/exegia-provider.tsx +0 -83
  580. package/src/lib/state/index.ts +0 -4
  581. package/src/lib/state/store.ts +0 -19
  582. package/src/lib/use-motion-panel.ts +0 -78
  583. package/src/lib/utils.test.ts +0 -22
  584. package/src/lib/utils.ts +0 -100
  585. package/src/specular.js +0 -4
  586. /package/{src/components/composed/chat/composer → dist-lib}/BEAUTIFUL-UI-LICENSE.txt +0 -0
  587. /package/{src/components/composed/chat/chart/evilcharts/LICENSE → dist-lib/EVILCHARTS-LICENSE.txt} +0 -0
@@ -1,1235 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- resolveTooltipPosition,
5
- roundnessClass,
6
- tooltipIndicatorHtml,
7
- tooltipRow,
8
- tooltipVariantClass,
9
- type TooltipPosition,
10
- type TooltipRoundness,
11
- type TooltipVariant,
12
- } from "./echarts-tooltip";
13
- import {
14
- DEFAULT_ECHARTS_RENDERER,
15
- buildChartCss,
16
- getColorsCount,
17
- resolveColors,
18
- withAlpha,
19
- type ChartConfig,
20
- type EChartsRenderer,
21
- type ResolvedColors,
22
- } from "./echarts-chart";
23
- import {
24
- Children,
25
- isValidElement,
26
- useCallback,
27
- useEffect,
28
- useId,
29
- useMemo,
30
- useRef,
31
- useState,
32
- type CSSProperties,
33
- type FC,
34
- type ReactNode,
35
- } from "react";
36
- import { TooltipComponent, type TooltipComponentOption } from "echarts/components";
37
- import { LegendOverlay, type LegendVariant } from "./echarts-legend";
38
- import { PieChart, type PieSeriesOption } from "echarts/charts";
39
- import { motion, useReducedMotion } from "motion/react";
40
- import type { ComposeOption } from "echarts/core";
41
- import * as echarts from "echarts/core";
42
-
43
- // Re-export the shared types that were previously declared inline here, so
44
- // existing consumers/examples keep importing them from the chart module.
45
- export type {
46
- ChartConfig,
47
- EChartsRenderer,
48
- LegendVariant,
49
- TooltipPosition,
50
- TooltipRoundness,
51
- TooltipVariant,
52
- };
53
-
54
- // Modular registration keeps the bundle lean — only the pieces this chart needs.
55
- // A pie has no coordinate system, so there is no GridComponent and no axes; the
56
- // tooltip is item-triggered. Never register GraphicComponent: the loading shimmer
57
- // and selection dim are per-sector itemStyle updates, not graphic overlays.
58
- echarts.use([PieChart, TooltipComponent]);
59
-
60
- type EChartsInstance = ReturnType<typeof echarts.init>;
61
-
62
- // The exact option surface this chart uses — a pie series plus the tooltip
63
- // component. Narrower than echarts' full EChartsOption, so a misspelled key fails
64
- // the compile instead of silently reaching setOption.
65
- type EChartsOption = ComposeOption<PieSeriesOption | TooltipComponentOption>;
66
-
67
- // Sector paint — structurally assignable to a pie datum's itemStyle. `color`
68
- // accepts the same solid-or-gradient value sectorPaint returns, and the optional
69
- // border fields carry the constant-width gap / overlap separator.
70
- type PieItemStyle = {
71
- color: string | echarts.graphic.LinearGradient;
72
- opacity: number;
73
- borderRadius: number;
74
- borderColor?: string;
75
- borderWidth?: number;
76
- };
77
-
78
- // ─────────────────────────────────────────────────────────────────────────────
79
- // Constants
80
- // ─────────────────────────────────────────────────────────────────────────────
81
-
82
- const REVEAL_DURATION = 1000; // intro draw-in length, in milliseconds (ECharts' default)
83
- // NOTE: the intro is ECharts' RAW default pie entrance (`animationType: "expansion"`
84
- // — sectors sweep out from the start angle). We only gate whether it plays; we do
85
- // not customize its easing, matching the area chart's "raw default" policy.
86
- const LOADING_ANIMATION_DURATION = 2000; // shimmer loop, in milliseconds
87
- const LOADING_SECTORS = 5; // skeleton sector count — Recharts twin uses 5 equal sectors
88
-
89
- const DEFAULT_INNER_RADIUS: number | string = 0;
90
- const DEFAULT_OUTER_RADIUS: number | string = "80%";
91
- const DEFAULT_CORNER_RADIUS = 0;
92
- const DEFAULT_PADDING_ANGLE = 0;
93
- const DEFAULT_START_ANGLE = 0;
94
- const DEFAULT_END_ANGLE = 360;
95
-
96
- const FALLBACK_COLOR = "rgba(120, 120, 120, 1)";
97
-
98
- // Overlapping sectors (negative paddingAngle) get a background-colored border to
99
- // separate the petals — the canvas analogue of the Recharts twin's
100
- // `stroke="var(--background)" strokeWidth={5}`.
101
- const OVERLAP_BORDER_WIDTH = 5;
102
-
103
- // Selecting a sector pops it radially OUTWARD from the center — the offset-slice
104
- // look from the official ECharts pie-pattern example. This is the pixel distance
105
- // the chosen sector translates along its own bisector; deselecting returns it.
106
- const SELECTED_OFFSET = 12;
107
-
108
- // The selected sector stays fully opaque; the others recede to this dimmed
109
- // opacity. Tuned to ~half the former 0.3 so the selected sector reads with more
110
- // contrast against the dimmed ones — the analogue of the area chart's dim-fill
111
- // halving (its dimmed fill went 0.2 → 0.1 while the selected state stays full).
112
- const DIMMED_OPACITY = 0.15;
113
-
114
- // Positive gaps between sectors are drawn as a CONSTANT-WIDTH background-colored
115
- // border (px), NOT an angular padAngle. An angular pad tapers to a wedge toward
116
- // the center; a border keeps every gap parallel-edged all the way from the rim to
117
- // the center. The px width tracks the requested `paddingAngle` for familiar sizing.
118
- function gapBorderWidth(paddingAngle: number): number {
119
- return Math.max(paddingAngle, 0);
120
- }
121
-
122
- // Resolves each sector's separator border. Negative paddingAngle keeps the
123
- // overlapping-petal look (a real angular overlap plus a wide separator); positive
124
- // paddingAngle becomes a constant-width gap; zero draws no border at all.
125
- function sectorBorder(
126
- paddingAngle: number,
127
- background: string,
128
- ): { borderColor: string; borderWidth: number } | null {
129
- if (paddingAngle < 0) return { borderColor: background, borderWidth: OVERLAP_BORDER_WIDTH };
130
- const width = gapBorderWidth(paddingAngle);
131
- if (width > 0) return { borderColor: background, borderWidth: width };
132
- return null;
133
- }
134
-
135
- // Loading shimmer opacities (× the foreground token's own alpha). A sine-feathered
136
- // window sweeps around the ring, brightening each sector from base → peak as it
137
- // passes — the angular twin of the area chart's swept clip window, and a match for
138
- // the Recharts twin's staggered sector pulse.
139
- const LOADING_BASE_OPACITY = 0.15; // resting sector fill, × foreground alpha
140
- const LOADING_PEAK_OPACITY = 0.5; // fill inside the sweep window, × foreground alpha
141
- const LOADING_SHIMMER_BAND = 0.28; // window half-width, fraction of the ring (0..1)
142
- const LOADING_SHIMMER_FEATHER = 0.22; // sine-eased edge softening of the window
143
-
144
- // ─────────────────────────────────────────────────────────────────────────────
145
- // Public types
146
- // ─────────────────────────────────────────────────────────────────────────────
147
-
148
- // The pie has a single fill style — a per-sector color gradient. Kept as a named
149
- // union for API parity with the Recharts twin (and room to grow).
150
- export type PieVariant = "gradient";
151
- // Where sector labels sit: "inside" draws value text on the sector (the default),
152
- // "outside" moves the sector's name past the rim with a leader line, matching the
153
- // classic ECharts pie (echarts.apache.org/examples/en/editor.html?c=pie-simple).
154
- export type LabelPosition = "inside" | "outside";
155
- // TooltipVariant, TooltipRoundness, TooltipPosition, LegendVariant, and ChartConfig
156
- // now live in the shared ./echarts/* modules and are imported +
157
- // re-exported at the top of this file.
158
- export type BackgroundVariant =
159
- | "dots"
160
- | "grid"
161
- | "cross-hatch"
162
- | "diagonal-lines"
163
- | "plus"
164
- | "falling-triangles"
165
- | "4-pointed-star"
166
- | "tiny-checkers"
167
- | "overlapping-circles"
168
- | "wiggle-lines"
169
- | "bubbles";
170
-
171
- export interface EChartsPieChartProps<TData extends Record<string, unknown>> {
172
- data: TData[]; // rows rendered by the chart — one sector each
173
- config: ChartConfig; // sector colors + labels, keyed by the sector name
174
- dataKey: keyof TData & string; // key holding each sector's numeric value
175
- nameKey: keyof TData & string; // key holding each sector's name
176
- className?: string; // extra classes for the chart container
177
- renderer?: EChartsRenderer; // rendering engine — canvas by default, or SVG
178
- // Master switch for the intro draw-in. Not present on the Recharts twin (which
179
- // hardcodes its animation); added here as the ECharts off-switch, mirroring the
180
- // ECharts area chart's `animation` prop. OS reduce-motion also disables it.
181
- animation?: boolean;
182
- defaultSelectedSector?: string | null; // sector selected on first render
183
- selectedSector?: string | null; // controlled selection — overrides internal state when set
184
- onSelectionChange?: (selection: { dataKey: string; value: number } | null) => void; // fires when the selected sector changes
185
- isLoading?: boolean; // shows the animated loading skeleton
186
- chartOptions?: Record<string, unknown>; // escape hatch merged over the built ECharts option
187
- children?: ReactNode; // declarative config — <Pie>, <Tooltip>, <Legend>, <Background>
188
- }
189
-
190
- // ─────────────────────────────────────────────────────────────────────────────
191
- // Composible parts — DECLARATIVE CONFIG. Every part renders `null`; the root
192
- // walks `children` by reference (child.type === Pie, …) to collect its props.
193
- // Presence semantics mirror the Recharts twin: omit a child and that part does
194
- // not render. These are never mounted into the tree — they only carry props.
195
- // ─────────────────────────────────────────────────────────────────────────────
196
-
197
- export interface PieProps {
198
- variant?: PieVariant; // fill style for the pie's sectors
199
- innerRadius?: number | string; // inner radius — set above 0 for a donut
200
- outerRadius?: number | string; // outer radius of the pie
201
- cornerRadius?: number; // border-radius of each sector in pixels
202
- paddingAngle?: number; // gap between sectors in degrees — negative overlaps them
203
- startAngle?: number; // angle the pie starts drawing from
204
- endAngle?: number; // angle the pie stops drawing at
205
- isClickable?: boolean; // lets sectors be selected by clicking them — the selected sector pops outward
206
- children?: ReactNode; // optional <Label> composition for sector labels
207
- }
208
-
209
- /**
210
- * The pie series. Declares its own shape (radii, angles, corner rounding,
211
- * padding) and clickability. Renders nothing — the root reads these props to
212
- * build the ECharts pie. When clickable, the selected sector pops radially
213
- * outward. Compose a <Label> inside it to draw labels on each sector.
214
- */
215
- const Pie: FC<PieProps> = () => null;
216
-
217
- export interface LabelProps {
218
- dataKey?: string; // data key for the label text — defaults to the pie's value key
219
- position?: LabelPosition; // "inside" (value on the sector) or "outside" (name past the rim, with a leader line)
220
- }
221
-
222
- /** Declares per-sector labels for the enclosing <Pie>. Renders nothing. */
223
- const Label: FC<LabelProps> = () => null;
224
-
225
- export interface TooltipProps {
226
- variant?: TooltipVariant; // visual style of the tooltip surface
227
- roundness?: TooltipRoundness; // border-radius of the tooltip
228
- defaultIndex?: number; // sector index shown by default with no hover
229
- position?: TooltipPosition; // "variable" follows the pointer (default); "fixed" pins the tooltip near the top and tracks the pointer's X
230
- }
231
-
232
- /** Presence enables the hover tooltip. Renders nothing. */
233
- const Tooltip: FC<TooltipProps> = () => null;
234
-
235
- export interface LegendProps {
236
- variant?: LegendVariant; // visual style of the legend indicators
237
- align?: "left" | "center" | "right"; // horizontal placement
238
- verticalAlign?: "top" | "middle" | "bottom"; // vertical placement
239
- isClickable?: boolean; // lets each entry toggle selection of its sector
240
- }
241
-
242
- /** Presence enables the HTML legend overlay. Renders nothing. */
243
- const Legend: FC<LegendProps> = () => null;
244
-
245
- export interface BackgroundProps {
246
- variant?: BackgroundVariant; // background pattern style
247
- }
248
-
249
- /** Presence draws a decorative SVG pattern behind the pie. Renders nothing. */
250
- const Background: FC<BackgroundProps> = () => null;
251
-
252
- // ─────────────────────────────────────────────────────────────────────────────
253
- // Children collection — walk the declarative config into plain objects the
254
- // option builder consumes. <Label> is read from the <Pie>'s own children; a
255
- // missing <Label> means sector labels do not render.
256
- // ─────────────────────────────────────────────────────────────────────────────
257
-
258
- type PieSlot = {
259
- variant: PieVariant;
260
- innerRadius: number | string;
261
- outerRadius: number | string;
262
- cornerRadius: number;
263
- paddingAngle: number;
264
- startAngle: number;
265
- endAngle: number;
266
- isClickable: boolean;
267
- labelDataKey: string | null; // null when no <Label> child is present
268
- labelPosition: LabelPosition; // where the <Label> sits — only meaningful when labelDataKey !== null
269
- };
270
-
271
- type TooltipSlot = {
272
- present: boolean;
273
- variant: TooltipVariant;
274
- roundness: TooltipRoundness;
275
- defaultIndex?: number;
276
- position: TooltipPosition;
277
- };
278
- type LegendSlot = {
279
- present: boolean;
280
- variant: LegendVariant;
281
- align: "left" | "center" | "right";
282
- verticalAlign: "top" | "middle" | "bottom";
283
- isClickable: boolean;
284
- };
285
- type BackgroundSlot = { present: boolean; variant: BackgroundVariant };
286
-
287
- type CollectedConfig = {
288
- pie: PieSlot | null;
289
- tooltip: TooltipSlot;
290
- legend: LegendSlot;
291
- background: BackgroundSlot;
292
- };
293
-
294
- function collectConfig(children: ReactNode): CollectedConfig {
295
- let pie: PieSlot | null = null;
296
- let tooltip: TooltipSlot = {
297
- present: false,
298
- variant: "default",
299
- roundness: "lg",
300
- position: "variable",
301
- };
302
- // Pie legend defaults differ from the area chart's: centered along the bottom.
303
- let legend: LegendSlot = {
304
- present: false,
305
- variant: "rounded-square",
306
- align: "center",
307
- verticalAlign: "bottom",
308
- isClickable: false,
309
- };
310
- let background: BackgroundSlot = { present: false, variant: "dots" };
311
-
312
- Children.forEach(children, (child) => {
313
- if (!isValidElement(child)) return;
314
- const type = child.type;
315
-
316
- if (type === Pie) {
317
- const props = child.props as PieProps;
318
- // A <Label> child (if any) declares per-sector labels.
319
- let labelDataKey: string | null = null;
320
- let labelPosition: LabelPosition = "inside";
321
- Children.forEach(props.children, (labelChild) => {
322
- if (!isValidElement(labelChild) || labelChild.type !== Label) return;
323
- const labelProps = labelChild.props as LabelProps;
324
- labelDataKey = labelProps.dataKey ?? "";
325
- labelPosition = labelProps.position ?? "inside";
326
- });
327
- pie = {
328
- variant: props.variant ?? "gradient",
329
- innerRadius: props.innerRadius ?? DEFAULT_INNER_RADIUS,
330
- outerRadius: props.outerRadius ?? DEFAULT_OUTER_RADIUS,
331
- cornerRadius: props.cornerRadius ?? DEFAULT_CORNER_RADIUS,
332
- paddingAngle: props.paddingAngle ?? DEFAULT_PADDING_ANGLE,
333
- startAngle: props.startAngle ?? DEFAULT_START_ANGLE,
334
- endAngle: props.endAngle ?? DEFAULT_END_ANGLE,
335
- isClickable: props.isClickable ?? false,
336
- labelDataKey,
337
- labelPosition,
338
- };
339
- } else if (type === Tooltip) {
340
- const props = child.props as TooltipProps;
341
- tooltip = {
342
- present: true,
343
- variant: props.variant ?? "default",
344
- roundness: props.roundness ?? "lg",
345
- defaultIndex: props.defaultIndex,
346
- position: props.position ?? "variable",
347
- };
348
- } else if (type === Legend) {
349
- const props = child.props as LegendProps;
350
- legend = {
351
- present: true,
352
- variant: props.variant ?? "rounded-square",
353
- align: props.align ?? "center",
354
- verticalAlign: props.verticalAlign ?? "bottom",
355
- isClickable: props.isClickable ?? false,
356
- };
357
- } else if (type === Background) {
358
- const props = child.props as BackgroundProps;
359
- background = { present: true, variant: props.variant ?? "dots" };
360
- }
361
- });
362
-
363
- return { pie, tooltip, legend, background };
364
- }
365
-
366
- // Color plumbing (ChartConfig, getColorsCount, buildChartCss, withAlpha,
367
- // ResolvedColors, resolveColors) now lives in ./echarts-chart and is
368
- // imported at the top of this file. The pie keeps its own DIAGONAL sectorPaint
369
- // below (the shared seriesPaint is a horizontal gradient).
370
-
371
- // ─────────────────────────────────────────────────────────────────────────────
372
- // Sector fill
373
- // ─────────────────────────────────────────────────────────────────────────────
374
-
375
- // Per-sector fill: a solid color for a single-color config, else a diagonal
376
- // top-left → bottom-right gradient across the sector's own bounding box. This
377
- // mirrors the Recharts twin's `RadialColorGradient` (a `linearGradient` with
378
- // x1/y1 = 0 and x2/y2 = 1). ECharts gradients are bbox-relative by default —
379
- // exactly what a per-sector gradient wants — so no `global` override is needed.
380
- function sectorPaint(slots: string[]): string | echarts.graphic.LinearGradient {
381
- if (slots.length <= 1) return slots[0] ?? FALLBACK_COLOR;
382
- const stops = slots.map((color, i) => ({ offset: i / (slots.length - 1), color }));
383
- return new echarts.graphic.LinearGradient(0, 0, 1, 1, stops);
384
- }
385
-
386
- // ─────────────────────────────────────────────────────────────────────────────
387
- // Loading skeleton helpers
388
- // ─────────────────────────────────────────────────────────────────────────────
389
-
390
- // Per-sector shimmer alpha: a sine-feathered window centered on `center` (both
391
- // values are ring fractions in [0, 1)). Sectors inside the window read at
392
- // `LOADING_PEAK_OPACITY`, outside at `LOADING_BASE_OPACITY`, with a sine falloff
393
- // across the feather so the sweep edge isn't a hard cut. Distance wraps around
394
- // the ring — the highlight travels continuously, like the Recharts twin's pulse.
395
- function loadingSectorAlpha(pos: number, center: number): number {
396
- const raw = Math.abs(pos - center);
397
- const dist = Math.min(raw, 1 - raw); // shortest way around the ring
398
- const half = LOADING_SHIMMER_BAND;
399
- const feather = LOADING_SHIMMER_FEATHER;
400
-
401
- if (dist >= half) return LOADING_BASE_OPACITY;
402
- if (dist <= half - feather) return LOADING_PEAK_OPACITY;
403
- // Sine-eased ramp — a linear one still reads as a hard cut.
404
- const t = 1 - (dist - (half - feather)) / feather;
405
- const eased = Math.sin((t * Math.PI) / 2);
406
- return LOADING_BASE_OPACITY + (LOADING_PEAK_OPACITY - LOADING_BASE_OPACITY) * eased;
407
- }
408
-
409
- // Tooltip styling (roundnessClass, tooltipVariantClass, tooltipRow,
410
- // tooltipIndicatorHtml) and the legend overlay (LegendOverlay + its indicators)
411
- // now live in ./echarts/{tooltip,legend} and are imported at the top
412
- // of this file.
413
-
414
- // ─────────────────────────────────────────────────────────────────────────────
415
- // Background overlay (SVG) — the Recharts twin renders its decorative pattern in
416
- // SVG, so we render the SAME SVG patterns as a layer BEHIND the transparent
417
- // ECharts canvas. Copied verbatim from the repo's <ChartBackground> so this file
418
- // depends on nothing outside `react`, `echarts`, and `motion`. The Tailwind
419
- // `text-border` classes resolve in the DOM; the blur-masked rect fades the edges.
420
- // ─────────────────────────────────────────────────────────────────────────────
421
-
422
- type PatternProps = { id: string };
423
-
424
- const BACKGROUND_PATTERNS: Record<BackgroundVariant, FC<PatternProps>> = {
425
- dots: ({ id }) => (
426
- <pattern id={id} x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
427
- <circle className="text-border" cx="2" cy="2" r="1" fill="currentColor" />
428
- </pattern>
429
- ),
430
- grid: ({ id }) => (
431
- <pattern id={id} x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
432
- <path
433
- className="text-border"
434
- d="M 20 0 L 0 0 0 20"
435
- fill="none"
436
- stroke="currentColor"
437
- strokeWidth="0.5"
438
- />
439
- </pattern>
440
- ),
441
- "cross-hatch": ({ id }) => (
442
- <pattern id={id} x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
443
- <path
444
- className="text-border/60 dark:text-border/50"
445
- d="M 0 0 L 20 20 M 20 0 L 0 20"
446
- fill="none"
447
- stroke="currentColor"
448
- strokeWidth="0.5"
449
- />
450
- </pattern>
451
- ),
452
- "diagonal-lines": ({ id }) => (
453
- <pattern
454
- id={id}
455
- x="0"
456
- y="0"
457
- width="6"
458
- height="6"
459
- patternUnits="userSpaceOnUse"
460
- patternTransform="rotate(45)"
461
- >
462
- <line
463
- className="text-border"
464
- x1="0"
465
- y1="0"
466
- x2="0"
467
- y2="6"
468
- stroke="currentColor"
469
- strokeWidth="0.5"
470
- />
471
- </pattern>
472
- ),
473
- plus: ({ id }) => (
474
- <pattern id={id} x="0" y="0" width="16" height="16" patternUnits="userSpaceOnUse">
475
- <path
476
- className="text-border"
477
- d="M 8 4 L 8 12 M 4 8 L 12 8"
478
- fill="none"
479
- stroke="currentColor"
480
- strokeWidth="0.5"
481
- strokeLinecap="round"
482
- />
483
- </pattern>
484
- ),
485
- "falling-triangles": ({ id }) => (
486
- <pattern id={id} x="0" y="0" width="18" height="36" patternUnits="userSpaceOnUse">
487
- <path
488
- className="text-border"
489
- d="M2 6h12L8 18 2 6zm18 36h12l-6 12-6-12z"
490
- transform="scale(0.5)"
491
- fill="currentColor"
492
- fillOpacity="0.4"
493
- />
494
- </pattern>
495
- ),
496
- "4-pointed-star": ({ id }) => (
497
- <pattern id={id} x="0" y="0" width="16" height="16" patternUnits="userSpaceOnUse">
498
- <polygon
499
- className="text-border"
500
- fillRule="evenodd"
501
- points="5 3 8 4 5 5 4 8 3 5 0 4 3 3 4 0 5 3"
502
- fill="currentColor"
503
- fillOpacity="0.4"
504
- />
505
- </pattern>
506
- ),
507
- "tiny-checkers": ({ id }) => (
508
- <pattern id={id} x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
509
- <path
510
- className="text-border"
511
- fillRule="evenodd"
512
- d="M0 0h4v4H0V0zm4 4h4v4H4V4z"
513
- fill="currentColor"
514
- fillOpacity="0.2"
515
- />
516
- </pattern>
517
- ),
518
- "overlapping-circles": ({ id }) => (
519
- <pattern id={id} x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
520
- <path
521
- className="text-border"
522
- fillRule="evenodd"
523
- d="M25 25c0-2.762 2.238-5 5-5s5 2.238 5 5-2.238 5-5 5c0 2.762-2.238 5-5 5s-5-2.238-5-5 2.238-5 5-5zM5 5c0-2.762 2.238-5 5-5s5 2.238 5 5-2.238 5-5 5c0 2.762-2.238 5-5 5S0 12.762 0 10s2.238-5 5-5zm5 4c2.209 0 4-1.791 4-4s-1.791-4-4-4-4 1.791-4 4 1.791 4 4 4zm20 20c2.209 0 4-1.791 4-4s-1.791-4-4-4-4 1.791-4 4 1.791 4 4 4z"
524
- fill="currentColor"
525
- fillOpacity="0.4"
526
- />
527
- </pattern>
528
- ),
529
- "wiggle-lines": ({ id }) => (
530
- <pattern
531
- id={id}
532
- x="0"
533
- y="0"
534
- width="52"
535
- height="26"
536
- patternUnits="userSpaceOnUse"
537
- patternTransform="scale(0.6)"
538
- >
539
- <path
540
- className="text-border"
541
- d="M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z"
542
- fill="currentColor"
543
- fillOpacity="0.4"
544
- />
545
- </pattern>
546
- ),
547
- bubbles: ({ id }) => (
548
- <pattern
549
- id={id}
550
- x="0"
551
- y="0"
552
- width="100"
553
- height="100"
554
- patternUnits="userSpaceOnUse"
555
- patternTransform="scale(0.6667)"
556
- >
557
- <path
558
- className="text-border"
559
- d="M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z"
560
- fill="currentColor"
561
- fillOpacity="0.4"
562
- fillRule="evenodd"
563
- />
564
- </pattern>
565
- ),
566
- };
567
-
568
- function BackgroundLayer({ variant }: { variant: BackgroundVariant }) {
569
- const baseId = useId().replace(/:/g, "");
570
- const patternId = `${baseId}-bg-${variant}`;
571
- const maskId = `${baseId}-bg-edge-fade`;
572
- const filterId = `${baseId}-bg-blur`;
573
- const PatternComponent = BACKGROUND_PATTERNS[variant];
574
-
575
- return (
576
- <svg
577
- className="pointer-events-none absolute inset-0 h-full w-full"
578
- aria-hidden
579
- preserveAspectRatio="none"
580
- >
581
- <defs>
582
- <PatternComponent id={patternId} />
583
- {/* Gaussian blur for a soft edge fade — a slightly inset white rect blurred
584
- into a mask leaves smooth transparent edges. */}
585
- <filter id={filterId}>
586
- <feGaussianBlur stdDeviation="25" />
587
- </filter>
588
- <mask id={maskId} maskUnits="userSpaceOnUse">
589
- <rect x="8%" y="20%" width="85%" height="60%" fill="white" filter={`url(#${filterId})`} />
590
- </mask>
591
- </defs>
592
- <rect width="100%" height="100%" fill={`url(#${patternId})`} mask={`url(#${maskId})`} />
593
- </svg>
594
- );
595
- }
596
-
597
- // ─────────────────────────────────────────────────────────────────────────────
598
- // Option builders — pure functions from a snapshot context to ECharts option
599
- // fragments. The component reads its refs ONCE per build into this context;
600
- // nothing below touches React state or the chart instance.
601
- // ─────────────────────────────────────────────────────────────────────────────
602
-
603
- type OptionBuildContext = {
604
- data: Record<string, unknown>[];
605
- config: ChartConfig;
606
- nameKey: string;
607
- dataKey: string;
608
- pie: PieSlot | null;
609
- selectedSector: string | null;
610
- tooltipSlot: TooltipSlot;
611
- legendSlot: LegendSlot;
612
- isLoading: boolean;
613
- resolved: ResolvedColors;
614
- };
615
-
616
- // The pie's vertical centre reserves room for the HTML legend overlay: a legend
617
- // along the bottom nudges the pie up, one along the top nudges it down. Kept as a
618
- // percentage so it tracks the container size (the legend is fixed-height text).
619
- function pieCenterY(legendSlot: LegendSlot): string {
620
- if (!legendSlot.present) return "50%";
621
- if (legendSlot.verticalAlign === "bottom") return "45%";
622
- if (legendSlot.verticalAlign === "top") return "55%";
623
- return "50%";
624
- }
625
-
626
- // Tooltip HTML builder, closed over the build context. The pie tooltip is
627
- // item-triggered, so each hover surfaces exactly one sector — its indicator,
628
- // label, and value, matching ChartTooltipContent with `hideLabel` (no header).
629
- function createTooltipFormatter(ctx: OptionBuildContext) {
630
- const { config, selectedSector, tooltipSlot } = ctx;
631
-
632
- return (params: unknown): string => {
633
- const p = (Array.isArray(params) ? params[0] : params) as {
634
- name?: string;
635
- value?: number | string;
636
- seriesId?: string;
637
- } | null;
638
- // The loading skeleton is a `__`-prefixed series and never surfaces a tooltip.
639
- if (!p || String(p.seriesId ?? "").startsWith("__")) return "";
640
-
641
- const name = String(p.name ?? "");
642
- const item = config[name];
643
- const colorsCount = item ? getColorsCount(item) : 1;
644
- const labelText = typeof item?.label === "string" ? item.label : name;
645
- const value = typeof p.value === "number" ? p.value.toLocaleString() : String(p.value ?? "");
646
- const dimmed = selectedSector != null && selectedSector !== name ? " opacity-30" : "";
647
-
648
- // The row shape matches the area chart's indicator + label + value, so it
649
- // reuses the shared tooltipRow/tooltipIndicatorHtml. The pie tooltip is
650
- // item-triggered with NO header (hideLabel), so it keeps its own no-header
651
- // shell — built from the shared roundnessClass/tooltipVariantClass — rather
652
- // than the header-carrying tooltipShell.
653
- const row = tooltipRow({
654
- indicatorHtml: tooltipIndicatorHtml(name, colorsCount),
655
- labelText,
656
- valueText: value,
657
- dimmed,
658
- });
659
-
660
- return `<div class="grid min-w-32 items-start gap-1.5 border border-border/50 px-2.5 py-1.5 text-xs shadow-xl ${roundnessClass[tooltipSlot.roundness]} ${tooltipVariantClass[tooltipSlot.variant]}">
661
- <div class="grid gap-1.5">${row}</div>
662
- </div>`;
663
- };
664
- }
665
-
666
- function buildTooltipOption(ctx: OptionBuildContext): TooltipComponentOption {
667
- const { tooltipSlot, isLoading } = ctx;
668
- // The pie tooltip is item-triggered (no axis, no cursor line), so it can't use
669
- // the shared tooltipBaseOption (which is trigger:"axis" with an axisPointer).
670
- // It keeps its own item-tooltip fields and wires the position prop directly
671
- // through the shared resolveTooltipPosition — "variable" → undefined (default
672
- // follow-the-pointer behavior), "fixed" → pinned near the top, tracking X.
673
- return {
674
- show: tooltipSlot.present && !isLoading,
675
- trigger: "item",
676
- confine: true,
677
- backgroundColor: "transparent",
678
- borderWidth: 0,
679
- padding: 0,
680
- extraCssText: "box-shadow:none;",
681
- displayTransition: false,
682
- position: resolveTooltipPosition(tooltipSlot.position),
683
- formatter: createTooltipFormatter(ctx),
684
- };
685
- }
686
-
687
- // The pie series. Each row becomes a sector whose fill is its own color gradient,
688
- // dimmed when another sector is selected, and separated from its neighbors by a
689
- // constant-width background border (see sectorBorder). The selected sector pops
690
- // radially outward via ECharts' native select state (selectedOffset).
691
- function buildPieSeries(ctx: OptionBuildContext): PieSeriesOption[] {
692
- const { data, config, nameKey, dataKey, pie, selectedSector, legendSlot, resolved } = ctx;
693
- if (!pie) return [];
694
- const { tokens } = resolved;
695
- const hasSelection = selectedSector !== null;
696
- // Selection (dim + pop-out) is only meaningful on a clickable pie.
697
- const border = sectorBorder(pie.paddingAngle, tokens.background);
698
-
699
- const sectors = data.map((row) => {
700
- const name = String(row[nameKey]);
701
- const slots = resolved.series[name] ?? [FALLBACK_COLOR];
702
- // Only a clickable pie dims — a static one never has a selection to dim from.
703
- const isSelected = pie.isClickable && selectedSector === name;
704
- const isDimmed = pie.isClickable && hasSelection && selectedSector !== name;
705
-
706
- const itemStyle: PieItemStyle = {
707
- color: sectorPaint(slots),
708
- opacity: isDimmed ? DIMMED_OPACITY : 1,
709
- borderRadius: pie.cornerRadius,
710
- };
711
- // Constant-width background gap (positive paddingAngle) or overlap separator
712
- // (negative). Parallel-edged from rim to center — no wedge-shaped taper.
713
- if (border) {
714
- itemStyle.borderColor = border.borderColor;
715
- itemStyle.borderWidth = border.borderWidth;
716
- }
717
-
718
- // The `selected` flag drives the native offset — React selection is the single
719
- // source of truth, re-applied on every notMerge push so it survives rebuilds.
720
- return { name, value: Number(row[dataKey]) || 0, itemStyle, selected: isSelected };
721
- });
722
-
723
- const showLabel = pie.labelDataKey !== null;
724
- const isOutside = pie.labelPosition === "outside";
725
- // An explicit <Label dataKey> always wins. Otherwise inside labels show the
726
- // sector's value (Recharts parity) and outside labels show the sector's name/
727
- // config label — matching the classic ECharts pie-simple outer labels.
728
- const explicitKey = pie.labelDataKey ? pie.labelDataKey : null;
729
- const labelFormatter = (labelParams: { dataIndex: number; name?: string; value?: unknown }) => {
730
- if (explicitKey) return String(data[labelParams.dataIndex]?.[explicitKey] ?? "");
731
- if (isOutside) {
732
- const item = config[String(labelParams.name ?? "")];
733
- return typeof item?.label === "string" ? item.label : String(labelParams.name ?? "");
734
- }
735
- return String(data[labelParams.dataIndex]?.[dataKey] ?? labelParams.value ?? "");
736
- };
737
-
738
- const label = {
739
- show: showLabel,
740
- // Inner value labels sit on the colored sector (background-colored text);
741
- // outer name labels sit past the rim in muted-foreground with a leader.
742
- position: (isOutside ? "outside" : "inner") as "outside" | "inner",
743
- color: isOutside ? tokens.mutedForeground : tokens.background,
744
- fontSize: 12,
745
- fontWeight: 500,
746
- formatter: labelFormatter,
747
- };
748
-
749
- return [
750
- {
751
- id: "pie",
752
- type: "pie",
753
- center: ["50%", pieCenterY(legendSlot)],
754
- radius: [pie.innerRadius, pie.outerRadius],
755
- startAngle: pie.startAngle,
756
- endAngle: pie.endAngle,
757
- // Recharts sweeps counterclockwise from 3 o'clock; ECharts angles share that
758
- // orientation, so `clockwise: false` reproduces the twin's sector order.
759
- clockwise: false,
760
- // Only a NEGATIVE paddingAngle reaches padAngle (petal overlap). Positive
761
- // gaps are drawn as constant-width borders instead — an angular pad would
762
- // taper to a wedge toward the center.
763
- padAngle: Math.min(pie.paddingAngle, 0),
764
- cursor: pie.isClickable ? "pointer" : "default",
765
- // No hover scale on ANY variant — hovering only surfaces the tooltip. The
766
- // pop-out below is the sole selection affordance, never a hover effect.
767
- emphasis: { scale: false },
768
- // Native select state: the chosen sector translates SELECTED_OFFSET px along
769
- // its bisector, away from the center (the offset-slice pie-pattern look).
770
- // Driven by each datum's `selected` flag; deselecting returns it.
771
- selectedMode: pie.isClickable ? "single" : false,
772
- selectedOffset: SELECTED_OFFSET,
773
- // Neutralize any default select styling — the selected sector keeps its
774
- // normal paint (inherited via state merge) and only its position moves.
775
- select: { itemStyle: {} },
776
- label,
777
- labelLine: isOutside
778
- ? {
779
- show: true,
780
- length: 14,
781
- length2: 14,
782
- smooth: false,
783
- // Leader lines drawn in a muted token, matching the docs aesthetic.
784
- lineStyle: { color: withAlpha(tokens.mutedForeground, 0.45), width: 1 },
785
- }
786
- : { show: false },
787
- data: sectors,
788
- },
789
- ];
790
- }
791
-
792
- // Loading skeleton — ONE gray ring of equal sectors regardless of the real data
793
- // (Recharts parity), swept by the shimmer rAF. Respects the pie's shape so a
794
- // donut skeleton stays a donut. The per-sector color is a placeholder; the rAF
795
- // loop retints each sector every frame.
796
- function buildLoadingOption(ctx: OptionBuildContext): EChartsOption {
797
- const { pie, legendSlot, resolved } = ctx;
798
- const { tokens } = resolved;
799
-
800
- const innerRadius = pie?.innerRadius ?? DEFAULT_INNER_RADIUS;
801
- const outerRadius = pie?.outerRadius ?? DEFAULT_OUTER_RADIUS;
802
- const cornerRadius = pie?.cornerRadius ?? DEFAULT_CORNER_RADIUS;
803
- const paddingAngle = pie?.paddingAngle ?? DEFAULT_PADDING_ANGLE;
804
- const startAngle = pie?.startAngle ?? DEFAULT_START_ANGLE;
805
- const endAngle = pie?.endAngle ?? DEFAULT_END_ANGLE;
806
-
807
- const border = sectorBorder(paddingAngle, tokens.background);
808
- const sectors = Array.from({ length: LOADING_SECTORS }, (_, i) => {
809
- const itemStyle: PieItemStyle = {
810
- color: withAlpha(tokens.foreground, LOADING_BASE_OPACITY),
811
- opacity: 1,
812
- borderRadius: cornerRadius,
813
- };
814
- // Same constant-width gap / overlap separator as the real pie.
815
- if (border) {
816
- itemStyle.borderColor = border.borderColor;
817
- itemStyle.borderWidth = border.borderWidth;
818
- }
819
- return { name: `__loading-${i}`, value: 1, itemStyle };
820
- });
821
-
822
- return {
823
- animation: false,
824
- tooltip: { show: false },
825
- series: [
826
- {
827
- id: "__loading",
828
- type: "pie",
829
- center: ["50%", pieCenterY(legendSlot)],
830
- radius: [innerRadius, outerRadius],
831
- startAngle,
832
- endAngle,
833
- clockwise: false,
834
- padAngle: Math.min(paddingAngle, 0),
835
- silent: true,
836
- emphasis: { scale: false },
837
- label: { show: false },
838
- labelLine: { show: false },
839
- data: sectors,
840
- },
841
- ],
842
- };
843
- }
844
-
845
- // ─────────────────────────────────────────────────────────────────────────────
846
- // Live imperative state — everything the ECharts event handlers and rAF loops
847
- // read or write OUTSIDE the React render cycle, grouped in one ref-stable object
848
- // so the whole imperative surface is visible at a glance. None of it is render
849
- // output, which is exactly why it is not React state.
850
- // ─────────────────────────────────────────────────────────────────────────────
851
-
852
- type LiveState = {
853
- resolved: ResolvedColors | null; // colors read off the live DOM — feeds builds and rAF loops
854
- hasRevealed: boolean; // the intro draw-in already played on this chart instance
855
- // Latest callbacks/flags for the imperative ECharts click handler.
856
- handlers: {
857
- isClickable: boolean;
858
- selectedSector: string | null;
859
- selectSector: (name: string | null) => void;
860
- };
861
- // Update-style re-push for paths that bypass React entirely (theme flips) —
862
- // set by the sync effect.
863
- repush: () => void;
864
- };
865
-
866
- // ─────────────────────────────────────────────────────────────────────────────
867
- // Component
868
- // ─────────────────────────────────────────────────────────────────────────────
869
-
870
- /**
871
- * Apache ECharts port of the EvilCharts pie chart, exposing a compound-as-config
872
- * API so its JSX reads identically to the Recharts twin. The root owns the data,
873
- * config, selection state, loading skeleton, and intro reveal; every visual part —
874
- * `<Pie>`, `<Tooltip>`, `<Legend>`, `<Background>` — is composed as a declarative
875
- * child that renders nothing. The root walks those children by reference and
876
- * drives a single imperative ECharts instance. Fully self-contained: its only
877
- * dependencies are `react`, `echarts`, and `motion`.
878
- */
879
- export function EChartsPieChart<TData extends Record<string, unknown>>({
880
- data,
881
- config,
882
- dataKey,
883
- nameKey,
884
- className,
885
- renderer = DEFAULT_ECHARTS_RENDERER,
886
- animation = true,
887
- defaultSelectedSector = null,
888
- selectedSector: selectedSectorProp,
889
- onSelectionChange,
890
- isLoading = false,
891
- chartOptions,
892
- children,
893
- }: EChartsPieChartProps<TData>) {
894
- const rawId = useId();
895
- const chartId = `chart-${rawId.replace(/:/g, "")}`;
896
-
897
- const containerRef = useRef<HTMLDivElement>(null);
898
- const mountRef = useRef<HTMLDivElement>(null);
899
- const echartsRef = useRef<EChartsInstance | null>(null);
900
-
901
- // The single imperative surface (see LiveState). `resolved` lives here rather
902
- // than in state: as state it would force an extra render pass and an effect
903
- // whose only job is to trigger the option push. The object identity is stable
904
- // for the component's lifetime.
905
- const live = useRef<LiveState>({
906
- resolved: null,
907
- hasRevealed: false,
908
- handlers: {
909
- isClickable: false,
910
- selectedSector: defaultSelectedSector,
911
- selectSector: () => {},
912
- },
913
- repush: () => {},
914
- }).current;
915
-
916
- const shouldReduceMotion = useReducedMotion();
917
-
918
- // Selection is controlled when the `selectedSector` prop is provided; otherwise
919
- // the internal state (seeded by defaultSelectedSector) drives it.
920
- const [internalSelectedSector, setSelectedSector] = useState<string | null>(
921
- defaultSelectedSector,
922
- );
923
- const selectedSector =
924
- selectedSectorProp !== undefined ? selectedSectorProp : internalSelectedSector;
925
-
926
- // ── Declarative config, collected from children by reference ─────────────────
927
- const collected = useMemo(() => collectConfig(children), [children]);
928
- const { pie, tooltip: tooltipSlot, legend: legendSlot, background: backgroundSlot } = collected;
929
-
930
- // Sector names in data order — the keys color resolution, the legend, and the
931
- // click handler all agree on. Config is keyed by these same names.
932
- const sectorKeys = useMemo(
933
- () => data.map((row) => String(row[nameKey as string])),
934
- [data, nameKey],
935
- );
936
-
937
- const css = useMemo(() => buildChartCss(chartId, config), [chartId, config]);
938
-
939
- // Sets selection state and notifies the parent with the sector's value.
940
- const selectSector = useCallback(
941
- (name: string | null) => {
942
- setSelectedSector(name);
943
- if (name === null) {
944
- onSelectionChange?.(null);
945
- return;
946
- }
947
- const item = data.find((row) => String(row[nameKey as string]) === name);
948
- onSelectionChange?.(
949
- item ? { dataKey: name, value: Number(item[dataKey as string]) || 0 } : null,
950
- );
951
- },
952
- [data, dataKey, nameKey, onSelectionChange],
953
- );
954
-
955
- // Refresh the click handler's snapshot of the latest callbacks/flags every render.
956
- live.handlers = {
957
- isClickable: pie?.isClickable ?? false,
958
- selectedSector,
959
- selectSector,
960
- };
961
-
962
- // ── Option builder ───────────────────────────────────────────────────────────
963
- // Thin orchestrator over the pure builders above: snapshot the resolved colors
964
- // into an OptionBuildContext, then assemble.
965
- const buildOption = useCallback((): EChartsOption => {
966
- const resolved = live.resolved;
967
- if (!resolved) return {};
968
-
969
- const ctx: OptionBuildContext = {
970
- data,
971
- config,
972
- nameKey: nameKey as string,
973
- dataKey: dataKey as string,
974
- pie,
975
- selectedSector,
976
- tooltipSlot,
977
- legendSlot,
978
- isLoading,
979
- resolved,
980
- };
981
-
982
- if (isLoading) return buildLoadingOption(ctx);
983
-
984
- return {
985
- animation: false,
986
- tooltip: buildTooltipOption(ctx),
987
- series: buildPieSeries(ctx),
988
- };
989
- }, [
990
- live,
991
- data,
992
- config,
993
- nameKey,
994
- dataKey,
995
- pie,
996
- selectedSector,
997
- tooltipSlot,
998
- legendSlot,
999
- isLoading,
1000
- ]);
1001
-
1002
- // ── Init + resize + theme observer (per renderer instance) ───────────────────
1003
- useEffect(() => {
1004
- const mount = mountRef.current;
1005
- const container = containerRef.current;
1006
- if (!mount || !container) return;
1007
-
1008
- const chart = echarts.init(mount, null, { renderer });
1009
- echartsRef.current = chart;
1010
-
1011
- const resizeObserver = new ResizeObserver(() => {
1012
- // Observers always fire once right after observe(). Repushing on that no-op
1013
- // fire would land one frame into the intro and stomp the reveal — only
1014
- // react when the renderer size actually changed. The pie has no
1015
- // renderer-sized textures, so a plain resize() (which re-lays the
1016
- // percentage geometry) is all a size change needs.
1017
- if (mount.clientWidth === chart.getWidth() && mount.clientHeight === chart.getHeight()) {
1018
- return;
1019
- }
1020
- chart.resize();
1021
- });
1022
- resizeObserver.observe(mount);
1023
-
1024
- // Light/dark flips change no React state — re-resolve and push directly.
1025
- const themeObserver = new MutationObserver(() => {
1026
- live.repush();
1027
- });
1028
- themeObserver.observe(document.documentElement, {
1029
- attributes: true,
1030
- attributeFilter: ["class"],
1031
- });
1032
-
1033
- chart.on("click", (params) => {
1034
- const { isClickable, selectedSector: selected, selectSector: select } = live.handlers;
1035
- if (!isClickable) return;
1036
- const p = params as { name?: string; seriesId?: string };
1037
- // Ignore the loading skeleton's `__`-prefixed series.
1038
- if (String(p.seriesId ?? "").startsWith("__")) return;
1039
- const name = p.name;
1040
- if (typeof name !== "string") return;
1041
- // Clicking the selected sector clears the selection, otherwise selects it.
1042
- select(selected === name ? null : name);
1043
- });
1044
-
1045
- return () => {
1046
- resizeObserver.disconnect();
1047
- themeObserver.disconnect();
1048
- chart.dispose();
1049
- echartsRef.current = null;
1050
- // The reveal guard belongs to the chart instance it guarded. Without this
1051
- // reset, StrictMode's dev-only mount→unmount→remount plays the entrance on
1052
- // the throwaway instance and the surviving one renders without it.
1053
- live.hasRevealed = false;
1054
- };
1055
- // eslint-disable-next-line react-hooks/exhaustive-deps
1056
- }, [renderer]);
1057
-
1058
- // ── Sync ECharts with props/theme/selection — resolve, build, push ────────────
1059
- useEffect(() => {
1060
- const chart = echartsRef.current;
1061
- const container = containerRef.current;
1062
- if (!chart || !container) return;
1063
-
1064
- // Colors come from the <style> committed just before this effect ran — read
1065
- // them here, right before the push, rather than round-tripping through state.
1066
- live.resolved = resolveColors(container, config, sectorKeys);
1067
-
1068
- const push = (withEntrance: boolean) => {
1069
- const option = buildOption();
1070
- const merged = chartOptions ? { ...option, ...chartOptions } : option;
1071
- Object.assign(merged, {
1072
- animation: withEntrance,
1073
- animationDuration: REVEAL_DURATION,
1074
- animationDurationUpdate: 0,
1075
- });
1076
- // chartOptions is an untyped escape hatch — the spread erases the option's
1077
- // shape, so re-assert it. The only cast in the file.
1078
- chart.setOption(merged as EChartsOption, { notMerge: true });
1079
- };
1080
-
1081
- // Intro reveal — ECharts' native pie expansion, enabled only for the first
1082
- // real render. Every later push (selection, theme) applies instantly, since
1083
- // notMerge would otherwise replay the entrance on each. A loading cycle
1084
- // re-arms it: the Recharts twin remounts its sectors after loading and
1085
- // replays the intro, so data → loading → data draws in again here too.
1086
- if (isLoading) live.hasRevealed = false;
1087
- const shouldReveal = !live.hasRevealed && !isLoading;
1088
- if (shouldReveal) live.hasRevealed = true;
1089
- const revealEnabled = animation && shouldReveal && !shouldReduceMotion;
1090
- push(revealEnabled);
1091
-
1092
- // Theme flips re-enter here without touching React: re-read the tokens (the
1093
- // .dark class changed) and push an update-style option.
1094
- live.repush = () => {
1095
- live.resolved = resolveColors(container, config, sectorKeys);
1096
- push(false);
1097
- };
1098
- }, [
1099
- live,
1100
- buildOption,
1101
- chartOptions,
1102
- isLoading,
1103
- animation,
1104
- shouldReduceMotion,
1105
- config,
1106
- renderer,
1107
- sectorKeys,
1108
- ]);
1109
-
1110
- // ── Default tooltip index — show a sector's tooltip with no hover ─────────────
1111
- useEffect(() => {
1112
- const chart = echartsRef.current;
1113
- if (!chart || isLoading || !tooltipSlot.present || tooltipSlot.defaultIndex == null) return;
1114
- // Dispatch after paint so the sector geometry exists to anchor the tooltip.
1115
- const raf = requestAnimationFrame(() => {
1116
- chart.dispatchAction({
1117
- type: "showTip",
1118
- seriesIndex: 0,
1119
- dataIndex: tooltipSlot.defaultIndex,
1120
- });
1121
- });
1122
- return () => cancelAnimationFrame(raf);
1123
- }, [isLoading, tooltipSlot.present, tooltipSlot.defaultIndex, renderer]);
1124
-
1125
- // ── Loading shimmer — rAF sweeps a bright window around the ring ──────────────
1126
- useEffect(() => {
1127
- const chart = echartsRef.current;
1128
- if (!chart || !isLoading) return;
1129
-
1130
- const cornerRadius = pie?.cornerRadius ?? DEFAULT_CORNER_RADIUS;
1131
- const paddingAngle = pie?.paddingAngle ?? DEFAULT_PADDING_ANGLE;
1132
-
1133
- let raf = 0;
1134
- const start = performance.now();
1135
- const tick = (now: number) => {
1136
- const phase = ((((now - start) / LOADING_ANIMATION_DURATION) % 1) + 1) % 1;
1137
- // Read tokens per frame, so a theme flip mid-loading retints the shimmer.
1138
- const foreground = live.resolved?.tokens.foreground ?? FALLBACK_COLOR;
1139
- const background = live.resolved?.tokens.background ?? FALLBACK_COLOR;
1140
-
1141
- // Rebuild the full itemStyle for each sector — setOption replaces a series'
1142
- // data array wholesale, so a partial datum would drop the border/rounding.
1143
- const border = sectorBorder(paddingAngle, background);
1144
- const sectors = Array.from({ length: LOADING_SECTORS }, (_, i) => {
1145
- const pos = (i + 0.5) / LOADING_SECTORS;
1146
- const itemStyle: PieItemStyle = {
1147
- color: withAlpha(foreground, loadingSectorAlpha(pos, phase)),
1148
- opacity: 1,
1149
- borderRadius: cornerRadius,
1150
- };
1151
- if (border) {
1152
- itemStyle.borderColor = border.borderColor;
1153
- itemStyle.borderWidth = border.borderWidth;
1154
- }
1155
- return { value: 1, itemStyle };
1156
- });
1157
-
1158
- chart.setOption(
1159
- { series: [{ id: "__loading", data: sectors }] },
1160
- { silent: true, lazyUpdate: true },
1161
- );
1162
- raf = requestAnimationFrame(tick);
1163
- };
1164
- raf = requestAnimationFrame(tick);
1165
- return () => cancelAnimationFrame(raf);
1166
- }, [live, isLoading, pie, renderer]);
1167
-
1168
- // ── Legend overlay position ──────────────────────────────────────────────────
1169
- const legendStyle: CSSProperties = {
1170
- position: "absolute",
1171
- left: 16,
1172
- right: 16,
1173
- pointerEvents: "auto",
1174
- ...(legendSlot.verticalAlign === "top"
1175
- ? { top: 12 }
1176
- : legendSlot.verticalAlign === "bottom"
1177
- ? { bottom: 12 }
1178
- : { top: "50%", transform: "translateY(-50%)" }),
1179
- };
1180
-
1181
- return (
1182
- <div
1183
- ref={containerRef}
1184
- data-chart={chartId}
1185
- className={`relative flex flex-col text-xs ${className ?? ""}`}
1186
- >
1187
- <style dangerouslySetInnerHTML={{ __html: css }} />
1188
-
1189
- <div className="relative min-h-0 w-full flex-1">
1190
- {backgroundSlot.present && !isLoading && (
1191
- <BackgroundLayer variant={backgroundSlot.variant} />
1192
- )}
1193
- <div ref={mountRef} className="relative h-full min-h-0 w-full" />
1194
- </div>
1195
-
1196
- {legendSlot.present && !isLoading && (
1197
- <LegendOverlay
1198
- seriesKeys={sectorKeys}
1199
- config={config}
1200
- variant={legendSlot.variant}
1201
- align={legendSlot.align}
1202
- verticalAlign={legendSlot.verticalAlign}
1203
- selectedKey={selectedSector}
1204
- hoveredKey={null}
1205
- isClickable={legendSlot.isClickable}
1206
- onToggle={(key) => selectSector(selectedSector === key ? null : key)}
1207
- style={legendStyle}
1208
- />
1209
- )}
1210
-
1211
- {isLoading && (
1212
- <div className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center">
1213
- <motion.div
1214
- initial={shouldReduceMotion ? false : { opacity: 0, scale: 0.92 }}
1215
- animate={{ opacity: 1, scale: 1 }}
1216
- transition={{ duration: 0.25, ease: "easeOut" }}
1217
- className="text-primary bg-background flex items-center justify-center gap-2 rounded-md border px-2 py-0.5 text-sm"
1218
- >
1219
- <div className="border-border border-t-primary h-3 w-3 animate-spin rounded-full border" />
1220
- <span>Loading</span>
1221
- </motion.div>
1222
- </div>
1223
- )}
1224
- </div>
1225
- );
1226
- }
1227
-
1228
- // Compound API: every part hangs off the root as a static member, so a consumer
1229
- // writes <EChartsPieChart.Pie/>, <EChartsPieChart.Tooltip/>, … from a single
1230
- // import — no colliding named marker exports when several charts share one file.
1231
- EChartsPieChart.Pie = Pie;
1232
- EChartsPieChart.Label = Label;
1233
- EChartsPieChart.Tooltip = Tooltip;
1234
- EChartsPieChart.Legend = Legend;
1235
- EChartsPieChart.Background = Background;