@bug-on/md3-react 2.0.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/.turbo/turbo-build.log +33 -0
  2. package/CHANGELOG.md +55 -0
  3. package/dist/index.css.d.ts +2 -0
  4. package/dist/index.d.mts +6127 -0
  5. package/dist/index.d.ts +6127 -71
  6. package/dist/index.js +1653 -614
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +1566 -547
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/material-symbols-cdn.css.d.ts +2 -0
  11. package/dist/material-symbols-self-hosted.css.d.ts +2 -0
  12. package/dist/typography.css.d.ts +2 -0
  13. package/package.json +22 -19
  14. package/scripts/copy-assets.js +82 -0
  15. package/src/assets/fonts/GoogleSansFlex-VariableFont.woff2 +0 -0
  16. package/src/assets/fonts/MaterialSymbolsOutlined-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  17. package/src/assets/fonts/MaterialSymbolsRounded-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  18. package/src/assets/fonts/MaterialSymbolsSharp-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  19. package/src/assets/loading-indicator.svg +19 -0
  20. package/src/assets/material-symbols-cdn.css +65 -0
  21. package/src/assets/material-symbols-self-hosted.css +90 -0
  22. package/src/css.d.ts +20 -0
  23. package/src/hooks/useClickOutside.ts +37 -0
  24. package/src/hooks/useMediaQuery.ts +28 -0
  25. package/src/hooks/useRipple.ts +88 -0
  26. package/src/index.css +23 -0
  27. package/src/index.ts +349 -0
  28. package/src/lib/material-symbols-preconnect.tsx +82 -0
  29. package/src/lib/theme-utils.ts +180 -0
  30. package/src/lib/utils.ts +6 -0
  31. package/src/test/button.test.tsx +59 -0
  32. package/src/test/icon.test.tsx +91 -0
  33. package/src/test/loading-indicator.test.tsx +128 -0
  34. package/src/test/progress-indicator.test.tsx +306 -0
  35. package/src/test/setup.ts +80 -0
  36. package/src/test/typography.test.tsx +206 -0
  37. package/src/types/index.ts +7 -0
  38. package/src/types/md3.ts +31 -0
  39. package/src/ui/Text.tsx +60 -0
  40. package/src/ui/__snapshots__/divider.test.tsx.snap +63 -0
  41. package/src/ui/app-bar/app-bar-column.tsx +99 -0
  42. package/src/ui/app-bar/app-bar-item-button.tsx +71 -0
  43. package/src/ui/app-bar/app-bar-items.test.tsx +89 -0
  44. package/src/ui/app-bar/app-bar-overflow-indicator.tsx +108 -0
  45. package/src/ui/app-bar/app-bar-row.tsx +104 -0
  46. package/src/ui/app-bar/app-bar.test.tsx +87 -0
  47. package/src/ui/app-bar/app-bar.tokens.ts +223 -0
  48. package/src/ui/app-bar/app-bar.types.ts +441 -0
  49. package/src/ui/app-bar/bottom-app-bar.test.tsx +42 -0
  50. package/src/ui/app-bar/bottom-app-bar.tsx +84 -0
  51. package/src/ui/app-bar/docked-toolbar.test.tsx +34 -0
  52. package/src/ui/app-bar/docked-toolbar.tsx +54 -0
  53. package/src/ui/app-bar/flexible-app-bar.test.tsx +75 -0
  54. package/src/ui/app-bar/hooks/use-app-bar-scroll.ts +110 -0
  55. package/src/ui/app-bar/hooks/use-flexible-app-bar.ts +123 -0
  56. package/{dist/ui/app-bar/index.d.ts → src/ui/app-bar/index.ts} +35 -2
  57. package/src/ui/app-bar/large-flexible-app-bar.tsx +165 -0
  58. package/src/ui/app-bar/medium-flexible-app-bar.tsx +167 -0
  59. package/src/ui/app-bar/search-app-bar.test.tsx +49 -0
  60. package/src/ui/app-bar/search-app-bar.tsx +176 -0
  61. package/src/ui/app-bar/search-view.tsx +227 -0
  62. package/src/ui/app-bar/small-app-bar.test.tsx +48 -0
  63. package/src/ui/app-bar/small-app-bar.tsx +203 -0
  64. package/src/ui/badge.test.tsx +345 -0
  65. package/src/ui/badge.tsx +282 -0
  66. package/src/ui/button-group.test.tsx +71 -0
  67. package/src/ui/button-group.tsx +350 -0
  68. package/src/ui/button.test.tsx +297 -0
  69. package/src/ui/button.tsx +669 -0
  70. package/src/ui/card.test.tsx +187 -0
  71. package/src/ui/card.tsx +259 -0
  72. package/src/ui/checkbox.test.tsx +423 -0
  73. package/src/ui/checkbox.tsx +525 -0
  74. package/src/ui/chip.test.tsx +292 -0
  75. package/src/ui/chip.tsx +548 -0
  76. package/src/ui/code-block.tsx +219 -0
  77. package/src/ui/dialog.test.tsx +300 -0
  78. package/src/ui/dialog.tsx +384 -0
  79. package/src/ui/divider.test.tsx +314 -0
  80. package/src/ui/divider.tsx +412 -0
  81. package/src/ui/drawer.tsx +240 -0
  82. package/src/ui/fab-menu.test.tsx +494 -0
  83. package/src/ui/fab-menu.tsx +739 -0
  84. package/src/ui/fab.test.tsx +232 -0
  85. package/src/ui/fab.tsx +505 -0
  86. package/src/ui/icon-button.test.tsx +515 -0
  87. package/src/ui/icon-button.tsx +525 -0
  88. package/src/ui/icon.test.tsx +197 -0
  89. package/src/ui/icon.tsx +179 -0
  90. package/src/ui/loading-indicator.test.tsx +73 -0
  91. package/src/ui/loading-indicator.tsx +312 -0
  92. package/src/ui/menu/context-menu.tsx +275 -0
  93. package/src/ui/menu/index.ts +77 -0
  94. package/src/ui/menu/menu-animations.ts +102 -0
  95. package/src/ui/menu/menu-context.tsx +99 -0
  96. package/src/ui/menu/menu-divider.tsx +47 -0
  97. package/src/ui/menu/menu-group.tsx +200 -0
  98. package/src/ui/menu/menu-item.tsx +294 -0
  99. package/src/ui/menu/menu-tokens.ts +208 -0
  100. package/src/ui/menu/menu-types.ts +313 -0
  101. package/src/ui/menu/menu.test.tsx +624 -0
  102. package/src/ui/menu/menu.tsx +289 -0
  103. package/src/ui/menu/sub-menu.tsx +223 -0
  104. package/src/ui/menu/vertical-menu.tsx +382 -0
  105. package/src/ui/navigation-rail.test.tsx +404 -0
  106. package/src/ui/navigation-rail.tsx +604 -0
  107. package/src/ui/progress-indicator/circular.tsx +248 -0
  108. package/src/ui/progress-indicator/hooks.ts +51 -0
  109. package/{dist/ui/progress-indicator/index.d.ts → src/ui/progress-indicator/index.tsx} +20 -2
  110. package/src/ui/progress-indicator/linear-flat.tsx +83 -0
  111. package/src/ui/progress-indicator/linear-wavy.tsx +243 -0
  112. package/src/ui/progress-indicator/linear.tsx +143 -0
  113. package/src/ui/progress-indicator/types.ts +158 -0
  114. package/src/ui/progress-indicator/utils.ts +73 -0
  115. package/src/ui/radio-button.test.tsx +407 -0
  116. package/src/ui/radio-button.tsx +551 -0
  117. package/src/ui/ripple.test.tsx +72 -0
  118. package/src/ui/ripple.tsx +234 -0
  119. package/src/ui/scroll-area.test.tsx +58 -0
  120. package/src/ui/scroll-area.tsx +139 -0
  121. package/src/ui/search/animated-placeholder.tsx +145 -0
  122. package/src/ui/search/hooks/use-search-keyboard.test.ts +202 -0
  123. package/src/ui/search/hooks/use-search-keyboard.ts +104 -0
  124. package/src/ui/search/hooks/use-search-view-focus.test.ts +96 -0
  125. package/src/ui/search/hooks/use-search-view-focus.ts +24 -0
  126. package/src/ui/search/index.ts +44 -0
  127. package/src/ui/search/search-bar.tsx +220 -0
  128. package/src/ui/search/search-context.tsx +42 -0
  129. package/src/ui/search/search-view-docked.tsx +194 -0
  130. package/src/ui/search/search-view-fullscreen.tsx +247 -0
  131. package/src/ui/search/search.test.tsx +233 -0
  132. package/src/ui/search/search.tokens.ts +134 -0
  133. package/src/ui/search/search.tsx +131 -0
  134. package/src/ui/search/search.types.ts +154 -0
  135. package/src/ui/search/trailing-action.tsx +49 -0
  136. package/src/ui/shared/constants.ts +122 -0
  137. package/{dist/ui/shared/touch-target.d.ts → src/ui/shared/touch-target.tsx} +13 -1
  138. package/src/ui/slider/hooks/useSliderMath.ts +195 -0
  139. package/{dist/ui/slider/index.d.ts → src/ui/slider/index.ts} +12 -1
  140. package/src/ui/slider/range-slider.tsx +561 -0
  141. package/src/ui/slider/slider-thumb.tsx +379 -0
  142. package/src/ui/slider/slider-track.tsx +912 -0
  143. package/src/ui/slider/slider.tokens.ts +189 -0
  144. package/src/ui/slider/slider.tsx +259 -0
  145. package/src/ui/slider/slider.types.ts +288 -0
  146. package/src/ui/snackbar/index.ts +20 -0
  147. package/src/ui/snackbar/snackbar.test.tsx +338 -0
  148. package/src/ui/snackbar/snackbar.tsx +476 -0
  149. package/{dist/ui/switch/index.d.ts → src/ui/switch/index.ts} +1 -0
  150. package/src/ui/switch/switch.stories.tsx +309 -0
  151. package/src/ui/switch/switch.test.tsx +243 -0
  152. package/src/ui/switch/switch.tokens.ts +89 -0
  153. package/src/ui/switch/switch.tsx +504 -0
  154. package/src/ui/switch/switch.types.ts +62 -0
  155. package/{dist/ui/tabs/index.d.ts → src/ui/tabs/index.ts} +8 -1
  156. package/src/ui/tabs/tab.tsx +407 -0
  157. package/src/ui/tabs/tabs-content.tsx +89 -0
  158. package/src/ui/tabs/tabs-list.tsx +146 -0
  159. package/src/ui/tabs/tabs.test.tsx +290 -0
  160. package/src/ui/tabs/tabs.tokens.ts +121 -0
  161. package/src/ui/tabs/tabs.tsx +229 -0
  162. package/src/ui/tabs/tabs.types.ts +185 -0
  163. package/{dist/ui/text-field/index.d.ts → src/ui/text-field/index.ts} +8 -1
  164. package/src/ui/text-field/subcomponents/active-indicator.tsx +67 -0
  165. package/src/ui/text-field/subcomponents/floating-label.tsx +161 -0
  166. package/src/ui/text-field/subcomponents/leading-icon.tsx +46 -0
  167. package/src/ui/text-field/subcomponents/outline-container.tsx +170 -0
  168. package/src/ui/text-field/subcomponents/prefix-suffix.tsx +59 -0
  169. package/src/ui/text-field/subcomponents/supporting-text.tsx +145 -0
  170. package/src/ui/text-field/subcomponents/trailing-icon.tsx +199 -0
  171. package/src/ui/text-field/text-field.test.tsx +454 -0
  172. package/src/ui/text-field/text-field.tokens.ts +104 -0
  173. package/src/ui/text-field/text-field.tsx +548 -0
  174. package/src/ui/text-field/text-field.types.ts +180 -0
  175. package/src/ui/theme-provider/index.tsx +190 -0
  176. package/src/ui/toc.test.tsx +108 -0
  177. package/src/ui/toc.tsx +172 -0
  178. package/src/ui/tooltip/plain-tooltip.tsx +63 -0
  179. package/src/ui/tooltip/rich-tooltip.tsx +94 -0
  180. package/src/ui/tooltip/tooltip-box.tsx +266 -0
  181. package/src/ui/tooltip/tooltip-caret-shape.tsx +68 -0
  182. package/src/ui/tooltip/tooltip.tokens.ts +26 -0
  183. package/src/ui/tooltip/tooltip.types.ts +70 -0
  184. package/src/ui/tooltip/use-tooltip-position.ts +208 -0
  185. package/src/ui/tooltip/use-tooltip-state.ts +41 -0
  186. package/src/ui/typography/__tests__/typography.test.tsx +170 -0
  187. package/{dist/ui/typography/index.d.ts → src/ui/typography/index.ts} +21 -3
  188. package/src/ui/typography/type-scale-tokens.ts +205 -0
  189. package/src/ui/typography/typography-key-tokens.ts +43 -0
  190. package/src/ui/typography/typography-tokens.ts +360 -0
  191. package/src/ui/typography/typography.css +22 -0
  192. package/src/ui/typography/typography.tsx +559 -0
  193. package/test-render.tsx +4 -0
  194. package/test-shadow.html +26 -0
  195. package/test_output.txt +164 -0
  196. package/test_output_v2.txt +5 -0
  197. package/tsconfig.build.json +10 -0
  198. package/tsconfig.json +18 -0
  199. package/tsup.config.ts +20 -0
  200. package/vitest.config.ts +11 -0
  201. package/dist/hooks/useClickOutside.d.ts +0 -8
  202. package/dist/hooks/useMediaQuery.d.ts +0 -11
  203. package/dist/hooks/useRipple.d.ts +0 -26
  204. package/dist/lib/material-symbols-preconnect.d.ts +0 -42
  205. package/dist/lib/theme-utils.d.ts +0 -63
  206. package/dist/lib/utils.d.ts +0 -2
  207. package/dist/types/index.d.ts +0 -1
  208. package/dist/types/md3.d.ts +0 -14
  209. package/dist/ui/app-bar/app-bar-column.d.ts +0 -28
  210. package/dist/ui/app-bar/app-bar-item-button.d.ts +0 -16
  211. package/dist/ui/app-bar/app-bar-overflow-indicator.d.ts +0 -18
  212. package/dist/ui/app-bar/app-bar-row.d.ts +0 -36
  213. package/dist/ui/app-bar/app-bar.tokens.d.ts +0 -184
  214. package/dist/ui/app-bar/app-bar.types.d.ts +0 -392
  215. package/dist/ui/app-bar/bottom-app-bar.d.ts +0 -31
  216. package/dist/ui/app-bar/docked-toolbar.d.ts +0 -25
  217. package/dist/ui/app-bar/hooks/use-app-bar-scroll.d.ts +0 -42
  218. package/dist/ui/app-bar/hooks/use-flexible-app-bar.d.ts +0 -37
  219. package/dist/ui/app-bar/large-flexible-app-bar.d.ts +0 -26
  220. package/dist/ui/app-bar/medium-flexible-app-bar.d.ts +0 -28
  221. package/dist/ui/app-bar/search-app-bar.d.ts +0 -43
  222. package/dist/ui/app-bar/search-view.d.ts +0 -54
  223. package/dist/ui/app-bar/small-app-bar.d.ts +0 -37
  224. package/dist/ui/badge.d.ts +0 -125
  225. package/dist/ui/button-group.d.ts +0 -59
  226. package/dist/ui/button.d.ts +0 -148
  227. package/dist/ui/card.d.ts +0 -62
  228. package/dist/ui/checkbox.d.ts +0 -82
  229. package/dist/ui/chip.d.ts +0 -110
  230. package/dist/ui/code-block.d.ts +0 -14
  231. package/dist/ui/dialog.d.ts +0 -111
  232. package/dist/ui/divider.d.ts +0 -164
  233. package/dist/ui/drawer.d.ts +0 -39
  234. package/dist/ui/dropdown.d.ts +0 -29
  235. package/dist/ui/fab-menu.d.ts +0 -204
  236. package/dist/ui/fab.d.ts +0 -162
  237. package/dist/ui/icon-button.d.ts +0 -131
  238. package/dist/ui/icon.d.ts +0 -88
  239. package/dist/ui/loading-indicator.d.ts +0 -42
  240. package/dist/ui/navigation-rail.d.ts +0 -29
  241. package/dist/ui/progress-indicator/circular.d.ts +0 -3
  242. package/dist/ui/progress-indicator/hooks.d.ts +0 -3
  243. package/dist/ui/progress-indicator/linear-flat.d.ts +0 -10
  244. package/dist/ui/progress-indicator/linear-wavy.d.ts +0 -18
  245. package/dist/ui/progress-indicator/linear.d.ts +0 -3
  246. package/dist/ui/progress-indicator/types.d.ts +0 -151
  247. package/dist/ui/progress-indicator/utils.d.ts +0 -3
  248. package/dist/ui/radio-button.d.ts +0 -106
  249. package/dist/ui/ripple.d.ts +0 -126
  250. package/dist/ui/scroll-area.d.ts +0 -27
  251. package/dist/ui/search/animated-placeholder.d.ts +0 -54
  252. package/dist/ui/search/hooks/use-search-keyboard.d.ts +0 -32
  253. package/dist/ui/search/hooks/use-search-view-focus.d.ts +0 -6
  254. package/dist/ui/search/index.d.ts +0 -27
  255. package/dist/ui/search/search-bar.d.ts +0 -32
  256. package/dist/ui/search/search-context.d.ts +0 -24
  257. package/dist/ui/search/search-view-docked.d.ts +0 -25
  258. package/dist/ui/search/search-view-fullscreen.d.ts +0 -36
  259. package/dist/ui/search/search.d.ts +0 -50
  260. package/dist/ui/search/search.tokens.d.ts +0 -112
  261. package/dist/ui/search/search.types.d.ts +0 -131
  262. package/dist/ui/search/trailing-action.d.ts +0 -9
  263. package/dist/ui/shared/constants.d.ts +0 -86
  264. package/dist/ui/slider/hooks/useSliderMath.d.ts +0 -101
  265. package/dist/ui/slider/range-slider.d.ts +0 -47
  266. package/dist/ui/slider/slider-thumb.d.ts +0 -33
  267. package/dist/ui/slider/slider-track.d.ts +0 -25
  268. package/dist/ui/slider/slider.d.ts +0 -60
  269. package/dist/ui/slider/slider.tokens.d.ts +0 -151
  270. package/dist/ui/slider/slider.types.d.ts +0 -259
  271. package/dist/ui/snackbar/index.d.ts +0 -6
  272. package/dist/ui/snackbar/snackbar.d.ts +0 -197
  273. package/dist/ui/switch/switch.d.ts +0 -30
  274. package/dist/ui/switch/switch.stories.d.ts +0 -48
  275. package/dist/ui/switch/switch.tokens.d.ts +0 -67
  276. package/dist/ui/switch/switch.types.d.ts +0 -59
  277. package/dist/ui/tabs/tab.d.ts +0 -43
  278. package/dist/ui/tabs/tabs-content.d.ts +0 -36
  279. package/dist/ui/tabs/tabs-list.d.ts +0 -40
  280. package/dist/ui/tabs/tabs.d.ts +0 -60
  281. package/dist/ui/tabs/tabs.tokens.d.ts +0 -94
  282. package/dist/ui/tabs/tabs.types.d.ts +0 -172
  283. package/dist/ui/text-field/subcomponents/active-indicator.d.ts +0 -24
  284. package/dist/ui/text-field/subcomponents/floating-label.d.ts +0 -43
  285. package/dist/ui/text-field/subcomponents/leading-icon.d.ts +0 -23
  286. package/dist/ui/text-field/subcomponents/outline-container.d.ts +0 -42
  287. package/dist/ui/text-field/subcomponents/prefix-suffix.d.ts +0 -24
  288. package/dist/ui/text-field/subcomponents/supporting-text.d.ts +0 -37
  289. package/dist/ui/text-field/subcomponents/trailing-icon.d.ts +0 -41
  290. package/dist/ui/text-field/text-field.d.ts +0 -49
  291. package/dist/ui/text-field/text-field.tokens.d.ts +0 -76
  292. package/dist/ui/text-field/text-field.types.d.ts +0 -126
  293. package/dist/ui/theme-provider/index.d.ts +0 -48
  294. package/dist/ui/toc.d.ts +0 -80
  295. package/dist/ui/tooltip/plain-tooltip.d.ts +0 -2
  296. package/dist/ui/tooltip/rich-tooltip.d.ts +0 -2
  297. package/dist/ui/tooltip/tooltip-box.d.ts +0 -2
  298. package/dist/ui/tooltip/tooltip-caret-shape.d.ts +0 -9
  299. package/dist/ui/tooltip/tooltip.tokens.d.ts +0 -26
  300. package/dist/ui/tooltip/tooltip.types.d.ts +0 -56
  301. package/dist/ui/tooltip/use-tooltip-position.d.ts +0 -8
  302. package/dist/ui/tooltip/use-tooltip-state.d.ts +0 -2
  303. package/dist/ui/typography/type-scale-tokens.d.ts +0 -162
  304. package/dist/ui/typography/typography-key-tokens.d.ts +0 -40
  305. package/dist/ui/typography/typography-tokens.d.ts +0 -220
  306. package/dist/ui/typography/typography.d.ts +0 -265
  307. /package/{dist/hooks/index.d.ts → src/hooks/index.ts} +0 -0
  308. /package/{dist/ui/tooltip/index.d.ts → src/ui/tooltip/index.ts} +0 -0
@@ -0,0 +1,312 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // MD3 Expressive Loading Indicator
8
+ //
9
+ // Spec references:
10
+ // • Loading Indicator Overview (190326 PDF)
11
+ // • Loading Indicator Specs (190326 PDF)
12
+ // • Loading Indicator Accessibility (190326 PDF)
13
+ // • LoadingIndicator.kt (Android reference)
14
+ //
15
+ // Two operational modes:
16
+ // 1. Indeterminate — no `progress` prop → 7-shape morph + SMIL rotation loop
17
+ // 2. Determinate — `progress` (0-1) → Circle→SoftBurst morph + counterclockwise rotation
18
+ //
19
+ // Responsive sizing: strictly [24dp, 240dp]. Container/indicator ratio is preserved.
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+
22
+ // ─── Indeterminate: Shape & Rotation Keyframe Data (from official MD3 SVG) ──
23
+ const SHAPE_KEY_TIMES =
24
+ "0; 0.14; 0.14; 0.29; 0.29; 0.43; 0.43; 0.57; 0.57; 0.71; 0.71; 0.86; 0.86; 1";
25
+
26
+ const SHAPE_KEY_SPLINES = [
27
+ "0.5 0.2 0 0.8",
28
+ "0.5 0.2 0 0.8",
29
+ "0.5 0.2 0 0.8",
30
+ "0.5 0.2 0 0.8",
31
+ "0.5 0.2 0 0.8",
32
+ "0.5 0.2 0 0.8",
33
+ "0.5 0.2 0 0.8",
34
+ "0.5 0.2 0 0.8",
35
+ "0.5 0.2 0 0.8",
36
+ "0.5 0.2 0 0.8",
37
+ "0.5 0.2 0 0.8",
38
+ "0.5 0.2 0 0.8",
39
+ "0.5 0.2 0 0.8",
40
+ ].join("; ");
41
+
42
+ // 7 MD3 morphing shapes, each repeated twice for a smooth hold-and-morph
43
+ const SHAPE_VALUES = [
44
+ "M20.9 10.4 21.4 9.5 21.9 8.7 22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17.1 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.4 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.5 38.9 26.2 39.6 27 40.2 27.7 40.5 28.7 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.9 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.5 9.1 21.8 8.4 21 7.8 20.3 7.5 19.3 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.1 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1Z",
45
+ "M20.3 8.6 21.1 8 22 7.6 23 7.3 23 7.3 24 7.2 25 7.3 25.9 7.5 26.8 8 27.6 8.6 28.4 9.1 28.4 9.1 29.3 9.6 30.3 9.8 31.3 9.9 32.3 10 33.3 10.2 34.2 10.6 34.2 10.6 35 11.2 35.7 11.9 36.3 12.7 36.7 13.6 36.9 14.6 37.2 15.5 37.2 15.5 37.6 16.5 38.2 17.3 38.9 18 39.6 18.7 40.2 19.5 40.6 20.4 40.6 20.4 40.9 21.3 41 22.3 40.9 23.3 40.6 24.3 40.2 25.2 39.8 26.1 39.8 26.1 39.5 27 39.4 28 39.5 29 39.6 30 39.5 31 39.3 32 39.3 32 38.9 32.9 38.3 33.7 37.6 34.4 36.8 35 35.9 35.4 35 35.8 35 35.8 34.1 36.3 33.4 37 32.9 37.9 32.3 38.7 31.6 39.4 30.8 40 30.8 40 29.9 40.4 28.9 40.7 27.9 40.8 27 40.7 26 40.4 25 40.1 25 40.1 24 40 23 40.1 22.1 40.4 21.1 40.7 20.1 40.8 19.1 40.7 19.1 40.7 18.2 40.5 17.3 40 16.4 39.5 15.7 38.8 15.2 37.9 14.6 37.1 14.6 37.1 13.9 36.4 13.1 35.8 12.2 35.4 11.3 35 10.5 34.4 9.7 33.8 9.7 33.8 9.1 32.9 8.7 32 8.5 31.1 8.4 30.1 8.5 29.1 8.6 28.1 8.6 28.1 8.5 27.1 8.3 26.1 7.8 25.2 7.4 24.3 7.1 23.4 7 22.4 7 22.4 7.1 21.4 7.3 20.4 7.8 19.5 8.3 18.7 9.1 18 9.8 17.3 9.8 17.3 10.4 16.5 10.8 15.6 11 14.6 11.3 13.7 11.7 12.8 12.2 11.9 12.2 11.9 12.9 11.2 13.8 10.7 14.7 10.2 15.6 10 16.6 9.9 17.6 9.8 17.6 9.8 18.6 9.6 19.5 9.2Z",
46
+ "M18.6 9.6 19.5 9.2 20.3 8.6 21.1 8 22 7.6 23 7.3 24 7.2 25 7.3 25.9 7.5 26.8 8 27.6 8.6 28.4 9.1 29.3 9.6 30.3 9.8 31.3 9.9 32.3 10 33.3 10.2 34.2 10.6 35 11.2 35.7 11.9 36.3 12.7 36.7 13.6 36.9 14.6 37.2 15.5 37.6 16.4 38.2 17.3 38.9 18 39.6 18.7 40.2 19.5 40.6 20.4 40.9 21.3 41 22.3 40.9 23.3 40.6 24.3 40.2 25.2 39.8 26.1 39.5 27 39.4 28 39.5 29 39.6 30 39.5 31 39.3 32 38.9 32.9 38.3 33.7 37.6 34.4 36.8 35 35.9 35.4 35 35.8 34.1 36.3 33.4 37 32.9 37.9 32.3 38.7 31.6 39.4 30.8 40 29.9 40.4 28.9 40.7 27.9 40.8 27 40.7 26 40.4 25 40.1 24 40 23 40.1 22.1 40.4 21.1 40.7 20.1 40.8 19.1 40.7 18.2 40.5 17.3 40 16.4 39.5 15.7 38.8 15.2 37.9 14.6 37.1 13.9 36.4 13.1 35.8 12.2 35.4 11.3 35 10.5 34.4 9.7 33.8 9.1 32.9 8.7 32 8.5 31.1 8.4 30.1 8.5 29.1 8.6 28.1 8.5 27.1 8.3 26.1 7.8 25.2 7.4 24.3 7.1 23.4 7 22.4 7.1 21.4 7.3 20.4 7.8 19.5 8.3 18.7 9.1 18 9.8 17.3 10.4 16.5 10.8 15.6 11 14.6 11.3 13.7 11.7 12.8 12.2 11.9 12.9 11.2 13.8 10.7 14.7 10.2 15.6 10 16.6 9.9 17.6 9.8Z",
47
+ "M18.6 9.9 19.5 9.4 20.3 8.8 21.1 8.2 22 7.8 23 7.6 23.9 7.5 24.9 7.6 25.9 7.8 26.8 8.2 27.6 8.7 28.5 9.3 29.3 9.9 30.1 10.5 30.9 11 31.7 11.6 32.5 12.2 33.3 12.8 33.7 13.1 34.1 13.3 34.9 13.9 35.7 14.5 36.6 15 37.4 15.6 38.2 16.2 39 16.8 39.7 17.5 40.2 18.3 40.7 19.2 40.9 20.1 41 21.1 40.9 22.1 40.7 23.1 40.3 24 40 24.9 39.7 25.9 39.4 26.8 39 27.8 38.7 28.7 38.4 29.6 38.1 30.6 37.8 31.5 37.5 32.5 37.2 33.4 36.9 34.4 36.6 35.3 36.2 36.2 35.7 37.1 35 37.8 34.3 38.4 33.4 38.9 32.5 39.3 31.5 39.5 30.5 39.5 30 39.5 29.5 39.5 28.5 39.5 27.5 39.5 26.5 39.5 25.5 39.4 24.5 39.4 23.6 39.4 22.6 39.4 21.6 39.5 20.6 39.5 19.6 39.5 18.6 39.5 17.6 39.5 16.6 39.5 15.6 39.3 14.7 39 13.8 38.5 13.1 37.9 12.4 37.2 11.9 36.3 11.5 35.4 11.2 34.5 10.9 33.5 10.6 32.6 10.3 31.6 10 30.7 9.7 29.7 9.3 28.8 9 27.9 8.7 26.9 8.4 26 8 25 7.7 24.1 7.4 23.2 7.1 22.2 7.1 21.7 7 21.2 7.1 20.2 7.3 19.3 7.7 18.4 8.3 17.5 8.9 16.8 9.7 16.2 10.5 15.6 11.4 15.1 12.2 14.5 13 14 13.8 13.4 14.6 12.8 15.4 12.3 16.2 11.7 17 11.1 17.8 10.5Z",
48
+ "M15.4 12.3 16.2 11.7 17 11.1 17.8 10.5 18.6 9.9 19.5 9.4 20.3 8.8 21.1 8.3 22 7.8 23 7.6 23.9 7.5 24.9 7.6 25.9 7.8 26.8 8.2 27.6 8.7 28.5 9.3 29.3 9.9 30.1 10.5 30.9 11 31.7 11.6 32.5 12.2 33.3 12.8 34.1 13.3 34.9 13.9 35.7 14.5 36.6 15 37.4 15.6 38.2 16.2 39 16.8 39.7 17.5 40.2 18.3 40.7 19.2 40.9 20.1 41 21.1 40.9 22.1 40.7 23.1 40.3 24 40 24.9 39.7 25.9 39.4 26.8 39 27.8 38.7 28.7 38.4 29.6 38.1 30.6 37.8 31.5 37.5 32.5 37.2 33.4 36.9 34.4 36.6 35.3 36.2 36.2 35.7 37.1 35 37.8 34.3 38.4 33.4 38.9 32.5 39.3 31.5 39.4 30.5 39.5 29.5 39.5 28.5 39.5 27.5 39.5 26.5 39.5 25.5 39.4 24.5 39.4 23.6 39.4 22.6 39.4 21.6 39.5 20.6 39.5 19.6 39.5 18.6 39.5 17.6 39.5 16.6 39.5 15.6 39.3 14.7 39 13.8 38.5 13.1 37.9 12.4 37.2 11.9 36.3 11.5 35.4 11.2 34.5 10.9 33.5 10.6 32.6 10.3 31.6 10 30.7 9.7 29.7 9.3 28.8 9 27.9 8.7 26.9 8.4 26 8 25 7.7 24.1 7.4 23.2 7.1 22.2 7 21.2 7.1 20.2 7.3 19.3 7.7 18.4 8.3 17.5 8.9 16.8 9.7 16.2 10.5 15.6 11.4 15.1 12.2 14.5 13 14 13.8 13.4 14.6 12.8Z",
49
+ "M17 12.8 17.7 12.1 18.5 11.5 19.3 10.9 20.1 10.5 20.2 10.4 21.1 10 22 9.7 23 9.4 24 9.2 25 9 26 9 27 9 27.6 9.1 28 9.1 28.9 9.3 29.9 9.6 30.9 9.9 31.8 10.3 32.6 10.8 33.5 11.3 34.3 11.9 34.6 12.2 35 12.6 35.7 13.3 36.4 14.1 36.9 14.9 37.4 15.8 37.9 16.6 38.3 17.6 38.6 18.5 38.6 18.7 38.8 19.5 38.9 20.5 39 21.5 39 22.5 38.9 23.5 38.7 24.5 38.5 25.4 38.2 26.3 38.2 26.4 37.8 27.3 37.4 28.2 36.8 29.1 36.2 29.9 35.6 30.6 34.9 31.3 34.2 32 33.8 32.5 33.5 32.8 32.8 33.5 32.1 34.2 31.4 34.9 30.7 35.6 29.9 36.2 29.1 36.8 28.2 37.3 27.9 37.5 27.4 37.8 26.4 38.2 25.5 38.5 24.5 38.7 23.5 38.9 22.5 39 21.5 39 20.5 38.9 20.4 38.9 19.5 38.8 18.6 38.6 17.6 38.3 16.7 37.9 15.8 37.5 14.9 37 14.1 36.4 13.4 35.8 13.3 35.8 12.6 35.1 12 34.3 11.3 33.5 10.8 32.7 10.3 31.8 9.9 30.9 9.6 30 9.4 29.3 9.3 29 9.1 28 9 27 9 26 9 25 9.2 24 9.4 23 9.6 22.1 9.8 21.7 10 21.1 10.4 20.2 10.9 19.4 11.4 18.5 12.1 17.8 12.7 17 13.4 16.3 14.2 15.6 14.2 15.5 14.9 14.9 15.6 14.2 16.3 13.5Z",
50
+ "M33.5 11.3 34.3 11.9 35 12.6 35.3 12.8 35.7 13.3 36.4 14.1 36.9 14.9 37.4 15.8 37.9 16.6 38.3 17.6 38.3 17.7 38.6 18.5 38.8 19.5 38.9 20.5 39 21.5 39 22.5 38.9 23.5 38.9 23.5 38.7 24.5 38.5 25.4 38.2 26.4 37.8 27.3 37.4 28.2 36.9 28.9 36.8 29.1 36.2 29.9 35.6 30.6 34.9 31.3 34.2 32 33.5 32.8 33.1 33.2 32.8 33.5 32.1 34.2 31.4 34.9 30.7 35.6 29.9 36.2 29.1 36.8 28.7 37 28.2 37.3 27.4 37.8 26.4 38.2 25.5 38.5 24.5 38.7 23.5 38.9 23.3 38.9 22.5 39 21.5 39 20.5 38.9 19.5 38.8 18.6 38.6 17.6 38.3 17.6 38.3 16.7 37.9 15.8 37.5 14.9 37 14.1 36.4 13.3 35.8 12.7 35.2 12.6 35.1 12 34.3 11.3 33.5 10.8 32.7 10.3 31.8 9.9 30.9 9.7 30.3 9.6 29.9 9.3 29 9.1 28 9 27 9 26 9 25 9.1 24.5 9.2 24 9.4 23 9.6 22.1 10 21.1 10.4 20.2 10.9 19.4 11.1 19.1 11.5 18.5 12.1 17.7 12.7 17 13.5 16.3 14.2 15.6 14.9 14.9 14.9 14.8 15.6 14.2 16.3 13.5 17 12.8 17.7 12.1 18.5 11.5 19.3 11 19.3 10.9 20.2 10.4 21.1 10 22 9.7 23 9.4 24 9.2 24.7 9.1 25 9 26 9 27 9 28 9.1 28.9 9.3 29.9 9.6 30.4 9.7 30.9 9.9 31.8 10.3 32.6 10.8Z",
51
+ "M33.2 11.1 34.2 11.2 35.1 11.4 36 11.9 36.6 12.7 36.8 13.7 36.9 14.7 36.9 15.7 37 16.7 37.1 17.7 37.3 18.6 37.9 19.4 38.5 20.2 39.2 20.9 39.8 21.7 40.5 22.4 40.9 23.3 41 24.3 40.7 25.2 40.1 26 39.4 26.8 38.8 27.5 38.1 28.3 37.5 29.1 37.1 30 37 31 37 31.9 36.9 32.9 36.8 33.9 36.7 34.9 36.2 35.8 35.5 36.4 34.5 36.8 33.6 36.8 32.6 36.9 31.6 37 30.6 37.1 29.6 37.2 28.8 37.7 28 38.4 27.3 39 26.5 39.7 25.8 40.3 24.9 40.8 23.9 41 23 40.8 22.1 40.2 21.4 39.6 20.6 38.9 19.9 38.3 19.1 37.6 18.3 37.2 17.3 37 16.3 37 15.3 36.9 14.3 36.8 13.3 36.7 12.4 36.4 11.7 35.7 11.3 34.8 11.2 33.8 11.1 32.8 11 31.8 11 30.8 10.9 29.9 10.4 29 9.8 28.2 9.1 27.5 8.5 26.7 7.8 26 7.3 25.1 7 24.2 7.1 23.2 7.6 22.3 8.2 21.6 8.9 20.8 9.5 20.1 10.2 19.3 10.7 18.5 10.9 17.5 11 16.6 11.1 15.6 11.1 14.6 11.2 13.6 11.5 12.6 12.1 11.9 13 11.4 13.9 11.2 14.9 11.1 15.9 11 16.9 11 17.9 10.9 18.8 10.6 19.6 10 20.4 9.3 21.1 8.6 21.9 8 22.6 7.4 23.6 7 24.6 7.1 25.5 7.5 26.2 8.1 27 8.7 27.7 9.4 28.5 10 29.3 10.6 30.2 10.9 31.2 11 32.2 11.1Z",
52
+ "M27.7 9.4 28.5 10 29.3 10.6 30.2 10.9 31.2 11 32.2 11.1 33.2 11.1 34.2 11.2 35.1 11.4 36 11.9 36.6 12.7 36.8 13.7 36.9 14.7 36.9 15.7 37 16.7 37.1 17.7 37.3 18.6 37.9 19.4 38.5 20.2 39.2 20.9 39.8 21.7 40.5 22.4 40.9 23.3 41 24.3 40.7 25.2 40.1 26 39.4 26.8 38.8 27.5 38.1 28.3 37.5 29.1 37.1 30 37 31 37 31.9 36.9 32.9 36.8 33.9 36.7 34.9 36.2 35.8 35.5 36.4 34.5 36.8 33.6 36.9 32.6 36.9 31.6 37 30.6 37.1 29.6 37.2 28.8 37.7 28 38.4 27.3 39 26.5 39.7 25.8 40.3 24.9 40.8 23.9 41 23 40.8 22.1 40.2 21.4 39.6 20.6 38.9 19.9 38.3 19.1 37.6 18.3 37.2 17.3 37 16.3 37 15.3 36.9 14.3 36.8 13.3 36.7 12.4 36.4 11.7 35.7 11.3 34.8 11.2 33.8 11.1 32.8 11 31.8 11 30.8 10.9 29.9 10.4 29 9.8 28.2 9.1 27.5 8.5 26.7 7.8 26 7.3 25.1 7 24.2 7.1 23.2 7.6 22.3 8.2 21.6 8.9 20.8 9.5 20.1 10.2 19.3 10.7 18.5 10.9 17.5 11 16.5 11.1 15.6 11.1 14.6 11.2 13.6 11.5 12.6 12.1 11.9 13 11.4 13.9 11.2 14.9 11.1 15.9 11 16.9 11 17.9 10.9 18.8 10.6 19.6 10 20.4 9.3 21.1 8.6 21.9 8 22.6 7.4 23.6 7 24.6 7.1 25.5 7.5 26.2 8.1 27 8.7Z",
53
+ "M27.9 10.6 28.8 10.3 29.8 10.1 30.8 10 31.8 10.1 32.7 10.3 33.7 10.6 34.6 11.1 34.8 11.3 35.4 11.7 36.1 12.4 36.7 13.1 37.2 14 37.6 14.9 37.9 15.9 38 16.9 38 17.9 37.8 18.8 37.5 19.8 37.1 20.7 36.8 21.6 36.5 22.6 36.4 23.6 36.4 24.4 36.4 24.6 36.5 25.5 36.8 26.5 37.2 27.4 37.6 28.3 37.8 29.3 38 30.3 38 31.3 37.8 32.3 37.6 33.2 37.2 34.1 36.6 35 36 35.7 35.3 36.4 34.4 37 34.1 37.2 33.6 37.4 32.6 37.8 31.6 37.9 30.6 38 29.7 37.9 28.7 37.7 27.8 37.3 26.8 36.9 25.9 36.6 24.9 36.4 23.9 36.4 22.9 36.4 22 36.6 21 37 20.1 37.4 20.1 37.4 19.2 37.7 18.2 37.9 17.2 38 16.2 37.9 15.3 37.7 14.3 37.4 13.4 36.9 12.6 36.3 11.9 35.6 11.3 34.9 10.8 34 10.4 33.1 10.1 32.1 10 31.1 10 30.6 10 30.1 10.2 29.2 10.5 28.2 10.9 27.3 11.2 26.4 11.5 25.4 11.6 24.4 11.6 23.4 11.5 22.5 11.2 21.5 10.8 20.6 10.4 19.7 10.2 18.7 10 17.7 10 16.7 10 16.6 10.2 15.7 10.4 14.8 10.8 13.9 11.4 13 12 12.3 12.7 11.6 13.6 11 14.4 10.6 15.4 10.2 16.4 10.1 17.4 10 18.3 10.1 19.3 10.3 20.2 10.7 20.9 11 21.2 11.1 22.1 11.4 23.1 11.6 24.1 11.6 25.1 11.6 26 11.4 27 11Z",
54
+ "M36 35.7 35.3 36.4 34.4 37 33.6 37.4 32.6 37.8 31.6 37.9 30.6 38 29.7 37.9 28.7 37.7 27.8 37.3 26.8 36.9 25.9 36.6 24.9 36.4 23.9 36.4 22.9 36.4 22 36.6 21 37 20.1 37.4 19.2 37.7 18.2 37.9 17.2 38 16.2 37.9 15.3 37.7 14.3 37.4 13.4 36.9 12.6 36.3 11.9 35.6 11.3 34.9 10.8 34 10.4 33.1 10.1 32.1 10 31.1 10 30.2 10.2 29.2 10.5 28.2 10.9 27.3 11.2 26.4 11.5 25.4 11.6 24.4 11.6 23.4 11.5 22.5 11.2 21.5 10.8 20.6 10.4 19.7 10.2 18.7 10 17.7 10 16.7 10.2 15.7 10.4 14.8 10.8 13.9 11.4 13 12 12.3 12.7 11.6 13.6 11 14.4 10.6 15.4 10.2 16.4 10.1 17.4 10 18.3 10.1 19.3 10.3 20.2 10.7 21.2 11.1 22.1 11.4 23.1 11.6 24.1 11.6 25.1 11.6 26 11.4 27 11 27.9 10.6 28.8 10.3 29.8 10.1 30.8 10 31.8 10.1 32.7 10.3 33.7 10.6 34.6 11.1 35.4 11.7 36.1 12.4 36.7 13.1 37.2 14 37.6 14.9 37.9 15.9 38 16.9 38 17.8 37.8 18.8 37.5 19.8 37.1 20.7 36.8 21.6 36.5 22.6 36.4 23.6 36.4 24.6 36.5 25.5 36.8 26.5 37.2 27.4 37.6 28.3 37.8 29.3 38 30.3 38 31.3 37.8 32.3 37.6 33.2 37.2 34.1 36.6 35Z",
55
+ "M32.1 32.1 31.4 32.8 30.7 33.5 29.9 34.1 29.1 34.7 28.3 35.3 27.6 35.8 27.5 35.8 26.6 36.4 25.8 36.8 24.9 37.3 24 37.7 23.1 38 22.1 38.3 21.2 38.6 20.2 38.8 19.2 38.9 18.2 39 17.2 39 16.6 38.9 16.3 38.9 15.3 38.7 14.3 38.4 13.4 38 12.5 37.5 11.7 36.9 11.1 36.3 10.5 35.5 10 34.6 9.6 33.7 9.3 32.7 9.1 31.7 9.1 31.4 9 30.8 9 29.8 9.1 28.8 9.2 27.8 9.4 26.8 9.7 25.9 10 24.9 10.3 24 10.7 23.1 11.2 22.2 11.6 21.4 12.2 20.5 12.2 20.4 12.7 19.7 13.3 18.9 13.9 18.1 14.5 17.3 15.2 16.6 15.9 15.9 16.6 15.2 17.3 14.5 18.1 13.9 18.9 13.3 19.7 12.7 20.4 12.2 20.5 12.2 21.4 11.6 22.2 11.2 23.1 10.7 24 10.3 24.9 10 25.9 9.7 26.8 9.4 27.8 9.2 28.8 9.1 29.8 9 30.8 9 31.4 9.1 31.7 9.1 32.7 9.3 33.7 9.6 34.6 10 35.5 10.5 36.3 11.1 36.9 11.7 37.5 12.5 38 13.4 38.4 14.3 38.7 15.3 38.9 16.3 38.9 16.6 39 17.2 39 18.2 38.9 19.2 38.8 20.2 38.6 21.2 38.3 22.1 38 23.1 37.7 24 37.3 24.9 36.8 25.8 36.4 26.6 35.8 27.5 35.8 27.6 35.3 28.3 34.7 29.1 34.1 29.9 33.5 30.7 32.8 31.4Z",
56
+ "M24.3 10.2 24.9 10 25.9 9.7 26.8 9.4 27.1 9.4 27.8 9.2 28.8 9.1 29.8 9 29.9 9 30.8 9 31.7 9.1 32.7 9.3 32.8 9.3 33.7 9.6 34.6 10 35.5 10.5 35.5 10.5 36.3 11.1 36.9 11.7 37.5 12.5 37.5 12.5 38 13.4 38.4 14.3 38.7 15.2 38.7 15.3 38.9 16.3 39 17.2 39 18.1 39 18.2 38.9 19.2 38.8 20.2 38.6 20.9 38.6 21.2 38.3 22.1 38 23.1 37.8 23.7 37.7 24 37.3 24.9 36.8 25.8 36.5 26.4 36.4 26.6 35.8 27.5 35.3 28.3 35 28.8 34.7 29.1 34.1 29.9 33.5 30.7 33.1 31.1 32.8 31.4 32.1 32.1 31.4 32.8 31.1 33.1 30.7 33.5 29.9 34.1 29.1 34.7 28.8 35 28.3 35.3 27.5 35.8 26.6 36.4 26.4 36.5 25.8 36.8 24.9 37.3 24 37.7 23.7 37.8 23.1 38 22.1 38.3 21.2 38.6 20.9 38.6 20.2 38.8 19.2 38.9 18.2 39 18.1 39 17.2 39 16.3 38.9 15.3 38.7 15.2 38.7 14.3 38.4 13.4 38 12.5 37.5 12.5 37.5 11.7 36.9 11.1 36.3 10.5 35.5 10.5 35.5 10 34.6 9.6 33.7 9.3 32.8 9.3 32.7 9.1 31.7 9 30.8 9 29.9 9 29.8 9.1 28.8 9.2 27.8 9.4 27.1 9.4 26.8 9.7 25.9 10 24.9 10.2 24.3 10.3 24 10.7 23.1 11.2 22.2 11.5 21.6 11.6 21.4 12.2 20.5 12.7 19.7 13 19.2 13.3 18.9 13.9 18.1 14.5 17.3 14.9 16.9 15.2 16.6 15.9 15.9 16.6 15.2 16.9 14.9 17.3 14.5 18.1 13.9 18.9 13.3 19.2 13 19.7 12.7 20.5 12.2 21.4 11.6 21.6 11.5 22.2 11.2 23.1 10.7 24 10.3Z",
57
+ "M22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.3 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.4 38.9 26.2 39.6 27 40.2 27.7 40.5 28.6 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.8 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.6 9.1 21.8 8.4 21 7.8 20.3 7.5 19.4 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.2 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1 20.9 10.4 21.4 9.5 21.9 8.7Z",
58
+ ].join(";");
59
+
60
+ const ROTATE_KEY_TIMES = "0; 0.14; 0.29; 0.43; 0.57; 0.71; 0.86; 1";
61
+ const ROTATE_KEY_SPLINES = [
62
+ "0.5 0.2 0 0.8",
63
+ "0.5 0.2 0 0.8",
64
+ "0.5 0.2 0 0.8",
65
+ "0.5 0.2 0 0.8",
66
+ "0.5 0.2 0 0.8",
67
+ "0.5 0.2 0 0.8",
68
+ "0.5 0.2 0 0.8",
69
+ ].join("; ");
70
+ const ROTATE_VALUES =
71
+ "0 24 24; 154 24 24; 309 24 24; 463 24 24; 617 24 24; 771 24 24; 926 24 24; 1080 24 24";
72
+
73
+ // ─── Determinate: Circle → SoftBurst path data (viewBox 4 4 40 40, cx=24, cy=24) ─
74
+ // Circle: perfect circle, r≈17 → matches the contained indicator's visual weight
75
+ const DETERMINATE_CIRCLE =
76
+ "M24 7 C34.49 7 41 13.51 41 24 C41 34.49 34.49 41 24 41 C13.51 41 7 34.49 7 24 C7 13.51 13.51 7 24 7 Z";
77
+
78
+ // SoftBurst: shape 1 from the indeterminate sequence (same visual as the spec)
79
+ const DETERMINATE_SOFT_BURST =
80
+ "M20.9 10.4 21.4 9.5 21.9 8.7 22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17.1 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.4 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.5 38.9 26.2 39.6 27 40.2 27.7 40.5 28.7 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.9 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.5 9.1 21.8 8.4 21 7.8 20.3 7.5 19.3 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.1 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1Z";
81
+
82
+ // ─── Types ────────────────────────────────────────────────────────────────────
83
+ export interface LoadingIndicatorProps
84
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
85
+ /**
86
+ * Visual style variant.
87
+ * - `uncontained` (default): bare indicator, no container background
88
+ * - `contained`: indicator inside a circular container
89
+ * (MD3 spec: 38dp container / 24dp active indicator)
90
+ */
91
+ variant?: "uncontained" | "contained";
92
+
93
+ /**
94
+ * Indicator size in dp (pixels). Clamped to the MD3 spec range [24dp, 240dp].
95
+ * Defaults to 48dp.
96
+ *
97
+ * Size guidelines:
98
+ * - Small displays: 24–48dp
99
+ * - Medium displays: 48–80dp
100
+ * - Large/XL displays (desktop): up to 240dp
101
+ *
102
+ * @default 48
103
+ */
104
+ size?: number;
105
+
106
+ /**
107
+ * Determinate progress value between 0 and 1.
108
+ * - **Omit** (default) for indeterminate mode: continuous morphing loop.
109
+ * - **Provide** for determinate mode: Circle→SoftBurst morph + counterclockwise rotation.
110
+ *
111
+ * @example `progress={0.7}` shows 70% progress
112
+ */
113
+ progress?: number;
114
+
115
+ /**
116
+ * Active indicator color override. Falls back to MD3 system color tokens.
117
+ * Supports any valid CSS color value or CSS variable.
118
+ * @example "#ff5722" | "var(--brand-color)"
119
+ */
120
+ color?: string;
121
+
122
+ /**
123
+ * Required accessible label describing what is loading.
124
+ * @example "Loading news article"
125
+ */
126
+ "aria-label": string;
127
+ }
128
+
129
+ // ─── Indeterminate SVG (pure SMIL, zero JS overhead) ─────────────────────────
130
+ const IndeterminateSvg = React.memo(function IndeterminateSvg({
131
+ size,
132
+ }: {
133
+ size: number;
134
+ }) {
135
+ // Trì hoãn chèn <animate> 1 frame bằng RequestAnimationFrame
136
+ // giúp bypass hoàn toàn lỗi Chromium đóng băng SMIL khi parent container đang thực hiện CSS transform/animation.
137
+ const [ready, setReady] = React.useState(false);
138
+ React.useEffect(() => {
139
+ const raf = requestAnimationFrame(() => setReady(true));
140
+ return () => cancelAnimationFrame(raf);
141
+ }, []);
142
+
143
+ return (
144
+ <svg
145
+ viewBox="4 4 40 40"
146
+ width={size}
147
+ height={size}
148
+ fill="currentColor"
149
+ xmlns="http://www.w3.org/2000/svg"
150
+ aria-hidden="true"
151
+ focusable="false"
152
+ >
153
+ <path d={SHAPE_VALUES.split(";")[0]}>
154
+ {ready && (
155
+ <>
156
+ <animate
157
+ attributeName="d"
158
+ dur="5s"
159
+ repeatCount="indefinite"
160
+ calcMode="spline"
161
+ keySplines={SHAPE_KEY_SPLINES}
162
+ keyTimes={SHAPE_KEY_TIMES}
163
+ values={SHAPE_VALUES}
164
+ />
165
+ <animateTransform
166
+ attributeName="transform"
167
+ attributeType="XML"
168
+ type="rotate"
169
+ dur="5s"
170
+ repeatCount="indefinite"
171
+ calcMode="spline"
172
+ keySplines={ROTATE_KEY_SPLINES}
173
+ keyTimes={ROTATE_KEY_TIMES}
174
+ values={ROTATE_VALUES}
175
+ />
176
+ </>
177
+ )}
178
+ </path>
179
+ </svg>
180
+ );
181
+ });
182
+
183
+ // ─── Determinate SVG (React-controlled morph + counterclockwise rotation) ────
184
+ // Uses CSS transition for smooth d attribute changes where supported (Chromium),
185
+ // with a style-based transform fallback for rotation.
186
+ const DeterminateSvg = React.memo(function DeterminateSvg({
187
+ size,
188
+ progress,
189
+ }: {
190
+ size: number;
191
+ progress: number;
192
+ }) {
193
+ // Clamp and interpolate progress between 0 (circle) and 1 (soft burst)
194
+ const p = Math.min(1, Math.max(0, progress));
195
+
196
+ // Counterclockwise rotation: 0° at 0% → -180° at 100% (per Android spec)
197
+ const rotation = -180 * p;
198
+
199
+ // The path morphs from circle (p=0) to soft-burst (p=1).
200
+ // We use a SMIL animate with begin="indefinite" that we snap to progress.
201
+ // For maximum compatibility, render the correct endpoint shape based on progress,
202
+ // relying on the CSS `d` property transition for smooth animation.
203
+ const pathD = p < 0.5 ? DETERMINATE_CIRCLE : DETERMINATE_SOFT_BURST;
204
+
205
+ return (
206
+ <svg
207
+ viewBox="4 4 40 40"
208
+ width={size}
209
+ height={size}
210
+ fill="currentColor"
211
+ xmlns="http://www.w3.org/2000/svg"
212
+ aria-hidden="true"
213
+ focusable="false"
214
+ style={{
215
+ transform: `rotate(${rotation}deg)`,
216
+ transition: "transform 0.3s ease",
217
+ }}
218
+ >
219
+ <path d={pathD} style={{ transition: "d 0.3s ease" }} />
220
+ </svg>
221
+ );
222
+ });
223
+
224
+ // ─── Main Component ───────────────────────────────────────────────────────────
225
+ export const LoadingIndicator = React.forwardRef<
226
+ HTMLDivElement,
227
+ LoadingIndicatorProps
228
+ >(
229
+ (
230
+ {
231
+ variant = "uncontained",
232
+ size = 48,
233
+ progress,
234
+ color,
235
+ className,
236
+ "aria-label": ariaLabel,
237
+ style,
238
+ ...restProps
239
+ },
240
+ ref,
241
+ ) => {
242
+ const isContained = variant === "contained";
243
+ const isDeterminate = progress !== undefined;
244
+
245
+ // MD3 spec: strict size range [24dp, 240dp]
246
+ const clampedSize = Math.min(240, Math.max(24, size));
247
+
248
+ // Proportional scaling based on the default 48dp spec size
249
+ const scaleFactor = clampedSize / 48;
250
+
251
+ // MD3 spec: contained → 38dp container, 24dp active indicator
252
+ // Ratio: 38/24 = 1.583 — preserved at all sizes
253
+ const containerSize = isContained ? 38 * scaleFactor : undefined;
254
+ const indicatorSize = isContained ? 24 * scaleFactor : clampedSize;
255
+
256
+ // MD3 color tokens (with per-variant defaults)
257
+ const activeColor = isContained
258
+ ? (color ?? "var(--md-sys-color-indicator-contained-active)")
259
+ : (color ?? "var(--md-sys-color-indicator-active)");
260
+
261
+ // aria-valuenow is only set in determinate mode (per ARIA spec)
262
+ const ariaNow = isDeterminate
263
+ ? Math.round(Math.min(1, Math.max(0, progress)) * 100)
264
+ : undefined;
265
+
266
+ return (
267
+ <div
268
+ ref={ref}
269
+ role="progressbar"
270
+ aria-label={ariaLabel}
271
+ aria-valuemin={0}
272
+ aria-valuemax={100}
273
+ aria-valuenow={ariaNow}
274
+ className={cn(
275
+ "inline-flex items-center justify-center shrink-0",
276
+ className,
277
+ )}
278
+ style={{
279
+ width: clampedSize,
280
+ height: clampedSize,
281
+ color: activeColor,
282
+ ...style,
283
+ }}
284
+ {...restProps}
285
+ >
286
+ {isContained ? (
287
+ <div
288
+ className="flex items-center justify-center rounded-full"
289
+ style={{
290
+ width: containerSize,
291
+ height: containerSize,
292
+ backgroundColor:
293
+ "var(--md-sys-color-indicator-contained-container)",
294
+ }}
295
+ >
296
+ {isDeterminate ? (
297
+ <DeterminateSvg size={indicatorSize} progress={progress} />
298
+ ) : (
299
+ <IndeterminateSvg size={indicatorSize} />
300
+ )}
301
+ </div>
302
+ ) : isDeterminate ? (
303
+ <DeterminateSvg size={indicatorSize} progress={progress} />
304
+ ) : (
305
+ <IndeterminateSvg size={indicatorSize} />
306
+ )}
307
+ </div>
308
+ );
309
+ },
310
+ );
311
+
312
+ LoadingIndicator.displayName = "LoadingIndicator";
@@ -0,0 +1,275 @@
1
+ // ─── MD3 Expressive Menu — ContextMenu (right-click / long-press) ─────────────
2
+ // Wraps @radix-ui/react-context-menu and provides MenuContext with
3
+ // menuPrimitive="context" so MenuItem automatically selects ContextMenu primitives.
4
+ //
5
+ // Visual styling is identical to Menu — baseline or expressive variant.
6
+ // Positioning, scroll lock, and outside click are all handled by Radix.
7
+ import * as RxContextMenu from "@radix-ui/react-context-menu";
8
+ import { AnimatePresence, m } from "motion/react";
9
+ import * as React from "react";
10
+ import { cn } from "../../lib/utils";
11
+ import { FAST_SPATIAL_SPRING } from "./menu-animations";
12
+ import { MenuProvider, useMenuContext } from "./menu-context";
13
+ import {
14
+ BASELINE_COLORS,
15
+ MENU_CONTAINER_SHAPE,
16
+ MENU_GROUP_GAP,
17
+ MENU_MAX_WIDTH,
18
+ MENU_MIN_WIDTH,
19
+ MENU_POPUP_PADDING_Y,
20
+ STANDARD_COLORS,
21
+ VIBRANT_COLORS,
22
+ } from "./menu-tokens";
23
+ import type {
24
+ ContextMenuContentProps,
25
+ ContextMenuProps,
26
+ ContextMenuTriggerProps,
27
+ } from "./menu-types";
28
+
29
+ // ─── ContextMenu (Root) ───────────────────────────────────────────────────────
30
+
31
+ /**
32
+ * Root of a context menu (right-click / long-press triggered popup).
33
+ *
34
+ * Wraps `@radix-ui/react-context-menu` Root and provides `MenuContext` with
35
+ * `menuPrimitive="context"` so nested `MenuItem` components automatically use
36
+ * ContextMenu Radix primitives for correct accessibility and keyboard navigation.
37
+ *
38
+ * @example
39
+ * // Baseline context menu
40
+ * <ContextMenu variant="baseline">
41
+ * <ContextMenuTrigger asChild>
42
+ * <div>Right-click me</div>
43
+ * </ContextMenuTrigger>
44
+ * <ContextMenuContent>
45
+ * <MenuItem>Copy</MenuItem>
46
+ * <MenuItem>Paste</MenuItem>
47
+ * </ContextMenuContent>
48
+ * </ContextMenu>
49
+ *
50
+ * @example
51
+ * // Expressive context menu with groups
52
+ * <ContextMenu variant="expressive">
53
+ * <ContextMenuTrigger asChild><canvas /></ContextMenuTrigger>
54
+ * <ContextMenuContent>
55
+ * <MenuGroup>
56
+ * <MenuItem>Cut</MenuItem>
57
+ * <MenuItem>Copy</MenuItem>
58
+ * </MenuGroup>
59
+ * <MenuGroup>
60
+ * <MenuItem>Paste</MenuItem>
61
+ * </MenuGroup>
62
+ * </ContextMenuContent>
63
+ * </ContextMenu>
64
+ */
65
+ export function ContextMenu({
66
+ children,
67
+ variant = "baseline",
68
+ colorVariant = "standard",
69
+ }: ContextMenuProps) {
70
+ const [open, setOpen] = React.useState(false);
71
+
72
+ return (
73
+ <MenuProvider
74
+ variant={variant}
75
+ colorVariant={colorVariant}
76
+ menuPrimitive="context"
77
+ open={open}
78
+ onOpenChange={setOpen}
79
+ >
80
+ <RxContextMenu.Root onOpenChange={setOpen}>{children}</RxContextMenu.Root>
81
+ </MenuProvider>
82
+ );
83
+ }
84
+ ContextMenu.displayName = "ContextMenu";
85
+
86
+ // ─── ContextMenuTrigger ───────────────────────────────────────────────────────
87
+
88
+ /**
89
+ * The trigger area for a ContextMenu. Right-click (or long-press on touch)
90
+ * within this area opens the context menu.
91
+ *
92
+ * Use `asChild` to apply trigger behavior to your own element without an
93
+ * extra wrapper `<span>`.
94
+ */
95
+ export const ContextMenuTrigger = React.forwardRef<
96
+ React.ComponentRef<typeof RxContextMenu.Trigger>,
97
+ ContextMenuTriggerProps &
98
+ React.ComponentPropsWithoutRef<typeof RxContextMenu.Trigger>
99
+ >(({ children, asChild = true, ...props }, ref) => (
100
+ <RxContextMenu.Trigger ref={ref} asChild={asChild} {...props}>
101
+ {children}
102
+ </RxContextMenu.Trigger>
103
+ ));
104
+ ContextMenuTrigger.displayName = "ContextMenuTrigger";
105
+
106
+ // ─── ContextMenuContent (popup panel) ─────────────────────────────────────────
107
+
108
+ /**
109
+ * The popup container for the context menu's contents.
110
+ *
111
+ * Renders into a portal via Radix. Uses Framer Motion `AnimatePresence` for
112
+ * smooth enter/exit animations. Transform-origin is automatically set by
113
+ * Radix via `--radix-context-menu-content-transform-origin`.
114
+ *
115
+ * Visual styling follows `variant` from `ContextMenuContext`:
116
+ * - `baseline` → CornerExtraSmall (4px), standard M3 styling
117
+ * - `expressive` → rounded-2xl, shape-morphing groups, elevation-2
118
+ *
119
+ * @param hasOverflow - Set true when using SubMenu to prevent clipping
120
+ */
121
+ export const ContextMenuContent = React.forwardRef<
122
+ React.ComponentRef<typeof RxContextMenu.Content>,
123
+ ContextMenuContentProps &
124
+ Omit<
125
+ React.ComponentPropsWithoutRef<typeof RxContextMenu.Content>,
126
+ "asChild"
127
+ >
128
+ >(
129
+ (
130
+ {
131
+ children,
132
+ hasOverflow = false,
133
+ colorVariant: propColorVariant,
134
+ separatorStyle = "gap",
135
+ className,
136
+ ...props
137
+ },
138
+ ref,
139
+ ) => {
140
+ const {
141
+ open,
142
+ variant,
143
+ colorVariant: contextColorVariant,
144
+ } = useMenuContext();
145
+ const colorVariant = propColorVariant ?? contextColorVariant;
146
+
147
+ const colors =
148
+ variant === "baseline"
149
+ ? BASELINE_COLORS
150
+ : colorVariant === "vibrant"
151
+ ? VIBRANT_COLORS
152
+ : STANDARD_COLORS;
153
+
154
+ const isExpressiveGap =
155
+ variant === "expressive" && separatorStyle === "gap";
156
+
157
+ const containerClassName =
158
+ variant === "expressive"
159
+ ? cn(
160
+ "z-50 flex flex-col w-full",
161
+ MENU_MIN_WIDTH,
162
+ MENU_MAX_WIDTH,
163
+ isExpressiveGap ? MENU_GROUP_GAP : "",
164
+ isExpressiveGap ? "bg-transparent" : colors.containerBg,
165
+ isExpressiveGap ? "" : "rounded-2xl",
166
+ isExpressiveGap ? "" : "elevation-2",
167
+ hasOverflow || isExpressiveGap
168
+ ? "overflow-visible"
169
+ : "overflow-hidden",
170
+ "outline-none",
171
+ className,
172
+ )
173
+ : cn(
174
+ "z-50 flex flex-col",
175
+ MENU_MIN_WIDTH,
176
+ MENU_MAX_WIDTH,
177
+ MENU_POPUP_PADDING_Y,
178
+ MENU_GROUP_GAP,
179
+ colors.containerBg,
180
+ MENU_CONTAINER_SHAPE,
181
+ "elevation-2",
182
+ hasOverflow ? "overflow-visible" : "overflow-hidden",
183
+ "outline-none",
184
+ className,
185
+ );
186
+
187
+ // Helper to recursively flatten fragments
188
+ const flattenChildren = (nodes: React.ReactNode): React.ReactElement[] => {
189
+ return React.Children.toArray(nodes).reduce(
190
+ (acc: React.ReactElement[], child) => {
191
+ if (React.isValidElement(child)) {
192
+ if (child.type === React.Fragment) {
193
+ return acc.concat(
194
+ flattenChildren(
195
+ (child as React.ReactElement<{ children?: React.ReactNode }>)
196
+ .props.children,
197
+ ),
198
+ );
199
+ }
200
+ acc.push(child as React.ReactElement);
201
+ }
202
+ return acc;
203
+ },
204
+ [],
205
+ );
206
+ };
207
+
208
+ let renderedChildren: React.ReactNode = children;
209
+
210
+ if (variant === "expressive") {
211
+ const validChildren = flattenChildren(children);
212
+ const groupCount = validChildren.length;
213
+
214
+ const enhancedChildren = validChildren.map((child, i) =>
215
+ React.cloneElement(
216
+ child as React.ReactElement<{
217
+ index?: number;
218
+ count?: number;
219
+ isGapVariant?: boolean;
220
+ className?: string;
221
+ }>,
222
+ {
223
+ index: i,
224
+ count: groupCount,
225
+ isGapVariant: isExpressiveGap,
226
+ },
227
+ ),
228
+ );
229
+
230
+ renderedChildren =
231
+ separatorStyle === "divider"
232
+ ? enhancedChildren.reduce<React.ReactNode[]>((acc, child, i) => {
233
+ if (i > 0) {
234
+ acc.push(
235
+ <hr
236
+ key={`divider-${(child as React.ReactElement).key || i}`}
237
+ className={cn(
238
+ "mx-3 my-0.5 h-px border-0 bg-m3-outline-variant",
239
+ )}
240
+ />,
241
+ );
242
+ }
243
+ acc.push(child);
244
+ return acc;
245
+ }, [])
246
+ : enhancedChildren;
247
+ }
248
+
249
+ return (
250
+ <AnimatePresence>
251
+ {open && (
252
+ <RxContextMenu.Portal forceMount>
253
+ <RxContextMenu.Content ref={ref} asChild forceMount {...props}>
254
+ <m.div
255
+ className={containerClassName}
256
+ initial={{ opacity: 0, scale: 0.95, y: -4 }}
257
+ animate={{ opacity: 1, scale: 1, y: 0 }}
258
+ exit={{ opacity: 0, scale: 0.95, y: -4 }}
259
+ transition={FAST_SPATIAL_SPRING}
260
+ style={{
261
+ ...(props.style as React.CSSProperties),
262
+ transformOrigin:
263
+ "var(--radix-context-menu-content-transform-origin)",
264
+ }}
265
+ >
266
+ {renderedChildren}
267
+ </m.div>
268
+ </RxContextMenu.Content>
269
+ </RxContextMenu.Portal>
270
+ )}
271
+ </AnimatePresence>
272
+ );
273
+ },
274
+ );
275
+ ContextMenuContent.displayName = "ContextMenuContent";
@@ -0,0 +1,77 @@
1
+ // ─── MD3 Expressive Menu — Barrel Export ─────────────────────────────────────
2
+
3
+ // Components — ContextMenu (right-click / long-press popup)
4
+ export {
5
+ ContextMenu,
6
+ ContextMenuContent,
7
+ ContextMenuTrigger,
8
+ } from "./context-menu";
9
+ // Components — Menu (dropdown popup)
10
+ export { Menu, MenuContent, MenuTrigger } from "./menu";
11
+
12
+ // Animation variants (for consumers extending animations)
13
+ export {
14
+ CHECK_ICON_VARIANTS,
15
+ FAST_EFFECTS_TRANSITION,
16
+ FAST_SPATIAL_SPRING,
17
+ MENU_CHECK_ICON_SIZE,
18
+ MENU_CONTAINER_VARIANTS,
19
+ SUBMENU_CONTAINER_VARIANTS,
20
+ } from "./menu-animations";
21
+
22
+ // Context (for advanced usage / extending)
23
+ export { MenuProvider, useMenuContext } from "./menu-context";
24
+ export { MenuDivider } from "./menu-divider";
25
+ export { MenuGroup } from "./menu-group";
26
+ export { MenuItem } from "./menu-item";
27
+
28
+ // Tokens (for consumers who need raw values)
29
+ export {
30
+ DIVIDER_COLOR,
31
+ DIVIDER_PADDING,
32
+ GROUP_SHAPES,
33
+ ITEM_SHAPE_CLASSES,
34
+ MENU_GROUP_GAP,
35
+ MENU_ICON_SIZE,
36
+ MENU_ITEM_MIN_HEIGHT,
37
+ MENU_MAX_WIDTH,
38
+ MENU_MIN_WIDTH,
39
+ STANDARD_COLORS,
40
+ VIBRANT_COLORS,
41
+ } from "./menu-tokens";
42
+
43
+ // Types
44
+ export type {
45
+ // ContextMenu
46
+ ContextMenuContentProps,
47
+ ContextMenuProps,
48
+ ContextMenuTriggerProps,
49
+ MenuColorVariant,
50
+ MenuContentProps,
51
+ MenuDividerProps,
52
+ MenuGroupPosition,
53
+ MenuGroupProps,
54
+ MenuItemPosition,
55
+ MenuItemProps,
56
+ MenuPrimitive,
57
+ MenuProps,
58
+ MenuTriggerProps,
59
+ MenuVariant,
60
+ SubMenuProps,
61
+ // Vertical Menu
62
+ VerticalMenuContentProps,
63
+ VerticalMenuDividerProps,
64
+ VerticalMenuGroupProps,
65
+ VerticalMenuProps,
66
+ VerticalMenuSeparatorStyle,
67
+ } from "./menu-types";
68
+
69
+ export { SubMenu } from "./sub-menu";
70
+
71
+ // Vertical Menu components (static, always-visible — unchanged)
72
+ export {
73
+ VerticalMenu,
74
+ VerticalMenuContent,
75
+ VerticalMenuDivider,
76
+ VerticalMenuGroup,
77
+ } from "./vertical-menu";