@bug-on/md3-react 2.0.2 → 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 (296) hide show
  1. package/.turbo/turbo-build.log +33 -0
  2. package/CHANGELOG.md +55 -0
  3. package/dist/index.css +23 -0
  4. package/dist/index.css.d.ts +2 -0
  5. package/dist/index.d.mts +6127 -0
  6. package/dist/index.d.ts +6127 -69
  7. package/dist/index.js +2536 -665
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2443 -603
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/material-symbols-cdn.css.d.ts +2 -0
  12. package/dist/material-symbols-self-hosted.css.d.ts +2 -0
  13. package/dist/typography.css.d.ts +2 -0
  14. package/package.json +23 -19
  15. package/scripts/copy-assets.js +82 -0
  16. package/src/assets/fonts/GoogleSansFlex-VariableFont.woff2 +0 -0
  17. package/src/assets/fonts/MaterialSymbolsOutlined-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  18. package/src/assets/fonts/MaterialSymbolsRounded-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  19. package/src/assets/fonts/MaterialSymbolsSharp-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  20. package/src/assets/loading-indicator.svg +19 -0
  21. package/src/assets/material-symbols-cdn.css +65 -0
  22. package/src/assets/material-symbols-self-hosted.css +90 -0
  23. package/src/css.d.ts +20 -0
  24. package/{dist/hooks/index.d.ts → src/hooks/index.ts} +1 -0
  25. package/src/hooks/useClickOutside.ts +37 -0
  26. package/src/hooks/useMediaQuery.ts +28 -0
  27. package/src/hooks/useRipple.ts +88 -0
  28. package/src/index.css +23 -0
  29. package/src/index.ts +349 -0
  30. package/src/lib/material-symbols-preconnect.tsx +82 -0
  31. package/src/lib/theme-utils.ts +180 -0
  32. package/src/lib/utils.ts +6 -0
  33. package/src/test/button.test.tsx +59 -0
  34. package/src/test/icon.test.tsx +91 -0
  35. package/src/test/loading-indicator.test.tsx +128 -0
  36. package/src/test/progress-indicator.test.tsx +306 -0
  37. package/src/test/setup.ts +80 -0
  38. package/src/test/typography.test.tsx +206 -0
  39. package/src/types/index.ts +7 -0
  40. package/src/types/md3.ts +31 -0
  41. package/src/ui/Text.tsx +60 -0
  42. package/src/ui/__snapshots__/divider.test.tsx.snap +63 -0
  43. package/src/ui/app-bar/app-bar-column.tsx +99 -0
  44. package/src/ui/app-bar/app-bar-item-button.tsx +71 -0
  45. package/src/ui/app-bar/app-bar-items.test.tsx +89 -0
  46. package/src/ui/app-bar/app-bar-overflow-indicator.tsx +108 -0
  47. package/src/ui/app-bar/app-bar-row.tsx +104 -0
  48. package/src/ui/app-bar/app-bar.test.tsx +87 -0
  49. package/src/ui/app-bar/app-bar.tokens.ts +223 -0
  50. package/src/ui/app-bar/app-bar.types.ts +441 -0
  51. package/src/ui/app-bar/bottom-app-bar.test.tsx +42 -0
  52. package/src/ui/app-bar/bottom-app-bar.tsx +84 -0
  53. package/src/ui/app-bar/docked-toolbar.test.tsx +34 -0
  54. package/src/ui/app-bar/docked-toolbar.tsx +54 -0
  55. package/src/ui/app-bar/flexible-app-bar.test.tsx +75 -0
  56. package/src/ui/app-bar/hooks/use-app-bar-scroll.ts +110 -0
  57. package/src/ui/app-bar/hooks/use-flexible-app-bar.ts +123 -0
  58. package/{dist/ui/app-bar/index.d.ts → src/ui/app-bar/index.ts} +35 -2
  59. package/src/ui/app-bar/large-flexible-app-bar.tsx +165 -0
  60. package/src/ui/app-bar/medium-flexible-app-bar.tsx +167 -0
  61. package/src/ui/app-bar/search-app-bar.test.tsx +49 -0
  62. package/src/ui/app-bar/search-app-bar.tsx +176 -0
  63. package/src/ui/app-bar/search-view.tsx +227 -0
  64. package/src/ui/app-bar/small-app-bar.test.tsx +48 -0
  65. package/src/ui/app-bar/small-app-bar.tsx +203 -0
  66. package/src/ui/badge.test.tsx +345 -0
  67. package/src/ui/badge.tsx +282 -0
  68. package/src/ui/button-group.test.tsx +71 -0
  69. package/src/ui/button-group.tsx +350 -0
  70. package/src/ui/button.test.tsx +297 -0
  71. package/src/ui/button.tsx +669 -0
  72. package/src/ui/card.test.tsx +187 -0
  73. package/src/ui/card.tsx +259 -0
  74. package/src/ui/checkbox.test.tsx +423 -0
  75. package/src/ui/checkbox.tsx +525 -0
  76. package/src/ui/chip.test.tsx +292 -0
  77. package/src/ui/chip.tsx +548 -0
  78. package/src/ui/code-block.tsx +219 -0
  79. package/src/ui/dialog.test.tsx +300 -0
  80. package/src/ui/dialog.tsx +384 -0
  81. package/src/ui/divider.test.tsx +314 -0
  82. package/src/ui/divider.tsx +412 -0
  83. package/src/ui/drawer.tsx +240 -0
  84. package/src/ui/fab-menu.test.tsx +494 -0
  85. package/src/ui/fab-menu.tsx +739 -0
  86. package/src/ui/fab.test.tsx +232 -0
  87. package/src/ui/fab.tsx +505 -0
  88. package/src/ui/icon-button.test.tsx +515 -0
  89. package/src/ui/icon-button.tsx +525 -0
  90. package/src/ui/icon.test.tsx +197 -0
  91. package/src/ui/icon.tsx +179 -0
  92. package/src/ui/loading-indicator.test.tsx +73 -0
  93. package/src/ui/loading-indicator.tsx +312 -0
  94. package/src/ui/menu/context-menu.tsx +275 -0
  95. package/src/ui/menu/index.ts +77 -0
  96. package/src/ui/menu/menu-animations.ts +102 -0
  97. package/src/ui/menu/menu-context.tsx +99 -0
  98. package/src/ui/menu/menu-divider.tsx +47 -0
  99. package/src/ui/menu/menu-group.tsx +200 -0
  100. package/src/ui/menu/menu-item.tsx +294 -0
  101. package/src/ui/menu/menu-tokens.ts +208 -0
  102. package/src/ui/menu/menu-types.ts +313 -0
  103. package/src/ui/menu/menu.test.tsx +624 -0
  104. package/src/ui/menu/menu.tsx +289 -0
  105. package/src/ui/menu/sub-menu.tsx +223 -0
  106. package/src/ui/menu/vertical-menu.tsx +382 -0
  107. package/src/ui/navigation-rail.test.tsx +404 -0
  108. package/src/ui/navigation-rail.tsx +604 -0
  109. package/src/ui/progress-indicator/circular.tsx +248 -0
  110. package/src/ui/progress-indicator/hooks.ts +51 -0
  111. package/{dist/ui/progress-indicator/index.d.ts → src/ui/progress-indicator/index.tsx} +20 -2
  112. package/src/ui/progress-indicator/linear-flat.tsx +83 -0
  113. package/src/ui/progress-indicator/linear-wavy.tsx +243 -0
  114. package/src/ui/progress-indicator/linear.tsx +143 -0
  115. package/src/ui/progress-indicator/types.ts +158 -0
  116. package/src/ui/progress-indicator/utils.ts +73 -0
  117. package/src/ui/radio-button.test.tsx +407 -0
  118. package/src/ui/radio-button.tsx +551 -0
  119. package/src/ui/ripple.test.tsx +72 -0
  120. package/src/ui/ripple.tsx +234 -0
  121. package/src/ui/scroll-area.test.tsx +58 -0
  122. package/src/ui/scroll-area.tsx +139 -0
  123. package/src/ui/search/animated-placeholder.tsx +145 -0
  124. package/src/ui/search/hooks/use-search-keyboard.test.ts +202 -0
  125. package/src/ui/search/hooks/use-search-keyboard.ts +104 -0
  126. package/src/ui/search/hooks/use-search-view-focus.test.ts +96 -0
  127. package/src/ui/search/hooks/use-search-view-focus.ts +24 -0
  128. package/src/ui/search/index.ts +44 -0
  129. package/src/ui/search/search-bar.tsx +220 -0
  130. package/src/ui/search/search-context.tsx +42 -0
  131. package/src/ui/search/search-view-docked.tsx +194 -0
  132. package/src/ui/search/search-view-fullscreen.tsx +247 -0
  133. package/src/ui/search/search.test.tsx +233 -0
  134. package/src/ui/search/search.tokens.ts +134 -0
  135. package/src/ui/search/search.tsx +131 -0
  136. package/src/ui/search/search.types.ts +154 -0
  137. package/src/ui/search/trailing-action.tsx +49 -0
  138. package/src/ui/shared/constants.ts +122 -0
  139. package/{dist/ui/shared/touch-target.d.ts → src/ui/shared/touch-target.tsx} +13 -1
  140. package/src/ui/slider/hooks/useSliderMath.ts +195 -0
  141. package/{dist/ui/slider/index.d.ts → src/ui/slider/index.ts} +12 -1
  142. package/src/ui/slider/range-slider.tsx +561 -0
  143. package/src/ui/slider/slider-thumb.tsx +379 -0
  144. package/src/ui/slider/slider-track.tsx +912 -0
  145. package/src/ui/slider/slider.tokens.ts +189 -0
  146. package/src/ui/slider/slider.tsx +259 -0
  147. package/src/ui/slider/slider.types.ts +288 -0
  148. package/src/ui/snackbar/index.ts +20 -0
  149. package/src/ui/snackbar/snackbar.test.tsx +338 -0
  150. package/src/ui/snackbar/snackbar.tsx +476 -0
  151. package/{dist/ui/switch/index.d.ts → src/ui/switch/index.ts} +1 -0
  152. package/src/ui/switch/switch.stories.tsx +309 -0
  153. package/src/ui/switch/switch.test.tsx +243 -0
  154. package/src/ui/switch/switch.tokens.ts +89 -0
  155. package/src/ui/switch/switch.tsx +504 -0
  156. package/src/ui/switch/switch.types.ts +62 -0
  157. package/{dist/ui/tabs/index.d.ts → src/ui/tabs/index.ts} +8 -1
  158. package/src/ui/tabs/tab.tsx +407 -0
  159. package/src/ui/tabs/tabs-content.tsx +89 -0
  160. package/src/ui/tabs/tabs-list.tsx +146 -0
  161. package/src/ui/tabs/tabs.test.tsx +290 -0
  162. package/src/ui/tabs/tabs.tokens.ts +121 -0
  163. package/src/ui/tabs/tabs.tsx +229 -0
  164. package/src/ui/tabs/tabs.types.ts +185 -0
  165. package/{dist/ui/text-field/index.d.ts → src/ui/text-field/index.ts} +8 -1
  166. package/src/ui/text-field/subcomponents/active-indicator.tsx +67 -0
  167. package/src/ui/text-field/subcomponents/floating-label.tsx +161 -0
  168. package/src/ui/text-field/subcomponents/leading-icon.tsx +46 -0
  169. package/src/ui/text-field/subcomponents/outline-container.tsx +170 -0
  170. package/src/ui/text-field/subcomponents/prefix-suffix.tsx +59 -0
  171. package/src/ui/text-field/subcomponents/supporting-text.tsx +145 -0
  172. package/src/ui/text-field/subcomponents/trailing-icon.tsx +199 -0
  173. package/src/ui/text-field/text-field.test.tsx +454 -0
  174. package/src/ui/text-field/text-field.tokens.ts +104 -0
  175. package/src/ui/text-field/text-field.tsx +548 -0
  176. package/src/ui/text-field/text-field.types.ts +180 -0
  177. package/src/ui/theme-provider/index.tsx +190 -0
  178. package/src/ui/toc.test.tsx +108 -0
  179. package/src/ui/toc.tsx +172 -0
  180. package/src/ui/tooltip/plain-tooltip.tsx +63 -0
  181. package/src/ui/tooltip/rich-tooltip.tsx +94 -0
  182. package/src/ui/tooltip/tooltip-box.tsx +266 -0
  183. package/src/ui/tooltip/tooltip-caret-shape.tsx +68 -0
  184. package/src/ui/tooltip/tooltip.tokens.ts +26 -0
  185. package/src/ui/tooltip/tooltip.types.ts +70 -0
  186. package/src/ui/tooltip/use-tooltip-position.ts +208 -0
  187. package/src/ui/tooltip/use-tooltip-state.ts +41 -0
  188. package/src/ui/typography/__tests__/typography.test.tsx +170 -0
  189. package/{dist/ui/typography/index.d.ts → src/ui/typography/index.ts} +21 -3
  190. package/src/ui/typography/type-scale-tokens.ts +205 -0
  191. package/src/ui/typography/typography-key-tokens.ts +43 -0
  192. package/src/ui/typography/typography-tokens.ts +360 -0
  193. package/src/ui/typography/typography.css +22 -0
  194. package/src/ui/typography/typography.tsx +559 -0
  195. package/test-render.tsx +4 -0
  196. package/test-shadow.html +26 -0
  197. package/test_output.txt +164 -0
  198. package/test_output_v2.txt +5 -0
  199. package/tsconfig.build.json +10 -0
  200. package/tsconfig.json +18 -0
  201. package/tsup.config.ts +20 -0
  202. package/vitest.config.ts +11 -0
  203. package/dist/hooks/useMediaQuery.d.ts +0 -11
  204. package/dist/hooks/useRipple.d.ts +0 -26
  205. package/dist/lib/material-symbols-preconnect.d.ts +0 -42
  206. package/dist/lib/theme-utils.d.ts +0 -63
  207. package/dist/lib/utils.d.ts +0 -2
  208. package/dist/types/index.d.ts +0 -1
  209. package/dist/types/md3.d.ts +0 -14
  210. package/dist/ui/app-bar/app-bar-column.d.ts +0 -28
  211. package/dist/ui/app-bar/app-bar-item-button.d.ts +0 -16
  212. package/dist/ui/app-bar/app-bar-overflow-indicator.d.ts +0 -18
  213. package/dist/ui/app-bar/app-bar-row.d.ts +0 -36
  214. package/dist/ui/app-bar/app-bar.tokens.d.ts +0 -184
  215. package/dist/ui/app-bar/app-bar.types.d.ts +0 -392
  216. package/dist/ui/app-bar/bottom-app-bar.d.ts +0 -31
  217. package/dist/ui/app-bar/docked-toolbar.d.ts +0 -25
  218. package/dist/ui/app-bar/hooks/use-app-bar-scroll.d.ts +0 -42
  219. package/dist/ui/app-bar/hooks/use-flexible-app-bar.d.ts +0 -37
  220. package/dist/ui/app-bar/large-flexible-app-bar.d.ts +0 -26
  221. package/dist/ui/app-bar/medium-flexible-app-bar.d.ts +0 -28
  222. package/dist/ui/app-bar/search-app-bar.d.ts +0 -43
  223. package/dist/ui/app-bar/search-view.d.ts +0 -54
  224. package/dist/ui/app-bar/small-app-bar.d.ts +0 -37
  225. package/dist/ui/badge.d.ts +0 -125
  226. package/dist/ui/button-group.d.ts +0 -59
  227. package/dist/ui/button.d.ts +0 -148
  228. package/dist/ui/card.d.ts +0 -62
  229. package/dist/ui/checkbox.d.ts +0 -82
  230. package/dist/ui/chip.d.ts +0 -110
  231. package/dist/ui/code-block.d.ts +0 -14
  232. package/dist/ui/dialog.d.ts +0 -111
  233. package/dist/ui/divider.d.ts +0 -164
  234. package/dist/ui/drawer.d.ts +0 -39
  235. package/dist/ui/dropdown.d.ts +0 -29
  236. package/dist/ui/fab-menu.d.ts +0 -204
  237. package/dist/ui/fab.d.ts +0 -162
  238. package/dist/ui/icon-button.d.ts +0 -131
  239. package/dist/ui/icon.d.ts +0 -88
  240. package/dist/ui/loading-indicator.d.ts +0 -42
  241. package/dist/ui/navigation-rail.d.ts +0 -29
  242. package/dist/ui/progress-indicator/circular.d.ts +0 -3
  243. package/dist/ui/progress-indicator/hooks.d.ts +0 -3
  244. package/dist/ui/progress-indicator/linear-flat.d.ts +0 -10
  245. package/dist/ui/progress-indicator/linear-wavy.d.ts +0 -18
  246. package/dist/ui/progress-indicator/linear.d.ts +0 -3
  247. package/dist/ui/progress-indicator/types.d.ts +0 -151
  248. package/dist/ui/progress-indicator/utils.d.ts +0 -3
  249. package/dist/ui/radio-button.d.ts +0 -106
  250. package/dist/ui/ripple.d.ts +0 -126
  251. package/dist/ui/scroll-area.d.ts +0 -27
  252. package/dist/ui/shared/constants.d.ts +0 -86
  253. package/dist/ui/slider/hooks/useSliderMath.d.ts +0 -101
  254. package/dist/ui/slider/range-slider.d.ts +0 -47
  255. package/dist/ui/slider/slider-thumb.d.ts +0 -33
  256. package/dist/ui/slider/slider-track.d.ts +0 -25
  257. package/dist/ui/slider/slider.d.ts +0 -60
  258. package/dist/ui/slider/slider.tokens.d.ts +0 -151
  259. package/dist/ui/slider/slider.types.d.ts +0 -259
  260. package/dist/ui/snackbar/index.d.ts +0 -6
  261. package/dist/ui/snackbar/snackbar.d.ts +0 -197
  262. package/dist/ui/switch/switch.d.ts +0 -30
  263. package/dist/ui/switch/switch.stories.d.ts +0 -48
  264. package/dist/ui/switch/switch.tokens.d.ts +0 -67
  265. package/dist/ui/switch/switch.types.d.ts +0 -59
  266. package/dist/ui/tabs/tab.d.ts +0 -43
  267. package/dist/ui/tabs/tabs-content.d.ts +0 -36
  268. package/dist/ui/tabs/tabs-list.d.ts +0 -40
  269. package/dist/ui/tabs/tabs.d.ts +0 -60
  270. package/dist/ui/tabs/tabs.tokens.d.ts +0 -94
  271. package/dist/ui/tabs/tabs.types.d.ts +0 -172
  272. package/dist/ui/text-field/subcomponents/active-indicator.d.ts +0 -24
  273. package/dist/ui/text-field/subcomponents/floating-label.d.ts +0 -43
  274. package/dist/ui/text-field/subcomponents/leading-icon.d.ts +0 -23
  275. package/dist/ui/text-field/subcomponents/outline-container.d.ts +0 -42
  276. package/dist/ui/text-field/subcomponents/prefix-suffix.d.ts +0 -24
  277. package/dist/ui/text-field/subcomponents/supporting-text.d.ts +0 -37
  278. package/dist/ui/text-field/subcomponents/trailing-icon.d.ts +0 -41
  279. package/dist/ui/text-field/text-field.d.ts +0 -49
  280. package/dist/ui/text-field/text-field.tokens.d.ts +0 -76
  281. package/dist/ui/text-field/text-field.types.d.ts +0 -126
  282. package/dist/ui/theme-provider/index.d.ts +0 -48
  283. package/dist/ui/toc.d.ts +0 -80
  284. package/dist/ui/tooltip/plain-tooltip.d.ts +0 -2
  285. package/dist/ui/tooltip/rich-tooltip.d.ts +0 -2
  286. package/dist/ui/tooltip/tooltip-box.d.ts +0 -2
  287. package/dist/ui/tooltip/tooltip-caret-shape.d.ts +0 -9
  288. package/dist/ui/tooltip/tooltip.tokens.d.ts +0 -26
  289. package/dist/ui/tooltip/tooltip.types.d.ts +0 -56
  290. package/dist/ui/tooltip/use-tooltip-position.d.ts +0 -8
  291. package/dist/ui/tooltip/use-tooltip-state.d.ts +0 -2
  292. package/dist/ui/typography/type-scale-tokens.d.ts +0 -162
  293. package/dist/ui/typography/typography-key-tokens.d.ts +0 -40
  294. package/dist/ui/typography/typography-tokens.d.ts +0 -220
  295. package/dist/ui/typography/typography.d.ts +0 -265
  296. /package/{dist/ui/tooltip/index.d.ts → src/ui/tooltip/index.ts} +0 -0
package/src/index.ts ADDED
@@ -0,0 +1,349 @@
1
+ export { useMediaQuery } from "./hooks/useMediaQuery";
2
+ // Hooks
3
+ /** @deprecated Use `useRippleState` (Framer Motion) from the main package instead. DOM-only ripple. */
4
+ export { useRipple as useDOMRipple } from "./hooks/useRipple";
5
+ export { MaterialSymbolsPreconnect } from "./lib/material-symbols-preconnect";
6
+ // Theme — MD3 Dynamic Color
7
+ export type { MD3ColorScheme, ThemeMode } from "./lib/theme-utils";
8
+ export { applyTheme, generateM3Theme } from "./lib/theme-utils";
9
+ // Utils
10
+ export { cn } from "./lib/utils";
11
+ // Types
12
+ export type {
13
+ MD3ColorStyle,
14
+ MD3Shape,
15
+ MD3Size,
16
+ PolymorphicProps,
17
+ PolymorphicRef,
18
+ } from "./types/md3";
19
+ // App Bar — MD3 Expressive App Bar system
20
+ export type {
21
+ AppBarColors,
22
+ AppBarColumnProps,
23
+ AppBarItem,
24
+ AppBarItemType,
25
+ AppBarMenuState,
26
+ AppBarOverflowIndicatorProps,
27
+ AppBarRowProps,
28
+ AppBarScrollBehavior,
29
+ BaseAppBarProps,
30
+ BottomAppBarProps,
31
+ DockedToolbarProps,
32
+ FlexibleAppBarProps,
33
+ SearchAppBarProps,
34
+ SearchBarVariant,
35
+ SearchViewProps,
36
+ SmallAppBarProps,
37
+ TitleAlignment,
38
+ UseAppBarScrollReturn,
39
+ } from "./ui/app-bar";
40
+ export {
41
+ APP_BAR_BOTTOM_SPRING,
42
+ APP_BAR_COLOR_TRANSITION,
43
+ APP_BAR_COLORS,
44
+ APP_BAR_ENTER_ALWAYS_SPRING,
45
+ APP_BAR_TITLE_FADE,
46
+ AppBarColumn,
47
+ AppBarOverflowIndicator,
48
+ AppBarRow,
49
+ AppBarTokens,
50
+ appBarTypography,
51
+ BottomAppBar,
52
+ DockedToolbar,
53
+ LargeFlexibleAppBar,
54
+ MediumFlexibleAppBar,
55
+ SEARCH_VIEW_SPRING,
56
+ SearchAppBar,
57
+ SearchView,
58
+ SearchViewContainer,
59
+ SmallAppBar,
60
+ useAppBarScroll,
61
+ } from "./ui/app-bar";
62
+ export type { BadgedBoxProps, BadgeProps } from "./ui/badge";
63
+ // Badge — MD3 Expressive status indicator
64
+ export { Badge, BadgedBox } from "./ui/badge";
65
+ export type { BaseButtonProps, ButtonProps } from "./ui/button";
66
+ export { Button } from "./ui/button";
67
+ export type { ButtonGroupProps } from "./ui/button-group";
68
+ export { ButtonGroup } from "./ui/button-group";
69
+ export type { CardProps } from "./ui/card";
70
+ export { Card } from "./ui/card";
71
+ // Checkbox — MD3 Expressive tri-state checkbox
72
+ export type {
73
+ CheckboxProps,
74
+ CheckboxState,
75
+ TriStateCheckboxProps,
76
+ } from "./ui/checkbox";
77
+ export { Checkbox, TriStateCheckbox } from "./ui/checkbox";
78
+ export type { ChipProps } from "./ui/chip";
79
+ export { Chip } from "./ui/chip";
80
+ export type { CodeBlockProps } from "./ui/code-block";
81
+ export { CodeBlock } from "./ui/code-block";
82
+ export type {
83
+ DialogContentProps,
84
+ DialogFullScreenContentProps,
85
+ DialogProps,
86
+ } from "./ui/dialog";
87
+ // Dialog
88
+ export {
89
+ Dialog,
90
+ DialogBody,
91
+ DialogClose,
92
+ DialogContent,
93
+ DialogDescription,
94
+ DialogFooter,
95
+ DialogFullScreenContent,
96
+ DialogHeader,
97
+ DialogIcon,
98
+ DialogOverlay,
99
+ DialogPortal,
100
+ DialogTitle,
101
+ DialogTrigger,
102
+ } from "./ui/dialog";
103
+ // Divider — MD3 Expressive divider line
104
+ export type { DividerProps } from "./ui/divider";
105
+ export { buildWavePath, Divider } from "./ui/divider";
106
+ export type { DrawerContentProps, DrawerProps } from "./ui/drawer";
107
+ // Drawer (Bottom Sheet)
108
+ export {
109
+ Drawer,
110
+ DrawerClose,
111
+ DrawerContent,
112
+ DrawerDescription,
113
+ DrawerFooter,
114
+ DrawerHeader,
115
+ DrawerOverlay,
116
+ DrawerPortal,
117
+ DrawerTitle,
118
+ DrawerTrigger,
119
+ } from "./ui/drawer";
120
+ export type { FABPositionProps, FABProps } from "./ui/fab";
121
+ // Floating Action Button
122
+ export { FAB, FABPosition } from "./ui/fab";
123
+ export type {
124
+ FABMenuItemData,
125
+ FABMenuItemProps,
126
+ FABMenuProps,
127
+ ToggleFABProps,
128
+ } from "./ui/fab-menu";
129
+ // FAB Menu
130
+ export { FABMenu, FABMenuItem, ToggleFAB } from "./ui/fab-menu";
131
+ // Icon — Material Symbols variable font
132
+ export type { IconProps } from "./ui/icon";
133
+ export { Icon } from "./ui/icon";
134
+ export type { BaseIconButtonProps, IconButtonProps } from "./ui/icon-button";
135
+ export { IconButton } from "./ui/icon-button";
136
+ export type { LoadingIndicatorProps } from "./ui/loading-indicator";
137
+ export { LoadingIndicator } from "./ui/loading-indicator";
138
+ // MD3 Expressive Menu — Standard + Vibrant + shape morphing
139
+ export type {
140
+ // ContextMenu
141
+ ContextMenuContentProps,
142
+ ContextMenuProps,
143
+ ContextMenuTriggerProps,
144
+ MenuColorVariant,
145
+ MenuContentProps,
146
+ MenuDividerProps,
147
+ MenuGroupPosition,
148
+ MenuGroupProps,
149
+ MenuItemPosition,
150
+ MenuItemProps,
151
+ MenuPrimitive,
152
+ MenuProps,
153
+ MenuTriggerProps,
154
+ MenuVariant,
155
+ SubMenuProps,
156
+ // Vertical Menu
157
+ VerticalMenuContentProps,
158
+ VerticalMenuDividerProps,
159
+ VerticalMenuGroupProps,
160
+ VerticalMenuProps,
161
+ VerticalMenuSeparatorStyle,
162
+ } from "./ui/menu";
163
+ export {
164
+ CHECK_ICON_VARIANTS,
165
+ ContextMenu,
166
+ ContextMenuContent,
167
+ ContextMenuTrigger,
168
+ DIVIDER_COLOR,
169
+ DIVIDER_PADDING,
170
+ FAST_EFFECTS_TRANSITION,
171
+ FAST_SPATIAL_SPRING,
172
+ GROUP_SHAPES,
173
+ ITEM_SHAPE_CLASSES,
174
+ MENU_CHECK_ICON_SIZE,
175
+ MENU_CONTAINER_VARIANTS,
176
+ MENU_GROUP_GAP,
177
+ MENU_ICON_SIZE,
178
+ MENU_ITEM_MIN_HEIGHT,
179
+ MENU_MAX_WIDTH,
180
+ MENU_MIN_WIDTH,
181
+ Menu,
182
+ MenuContent,
183
+ MenuDivider,
184
+ MenuGroup,
185
+ MenuItem,
186
+ MenuProvider,
187
+ MenuTrigger,
188
+ STANDARD_COLORS,
189
+ SUBMENU_CONTAINER_VARIANTS,
190
+ SubMenu,
191
+ useMenuContext,
192
+ // Vertical Menu (static, always-visible)
193
+ VerticalMenu,
194
+ VerticalMenuContent,
195
+ VerticalMenuDivider,
196
+ VerticalMenuGroup,
197
+ VIBRANT_COLORS,
198
+ } from "./ui/menu";
199
+ // Navigation Rail
200
+ export type {
201
+ NavigationRailItemProps,
202
+ NavigationRailLabelVisibility,
203
+ NavigationRailProps,
204
+ NavigationRailVariant,
205
+ } from "./ui/navigation-rail";
206
+ export { NavigationRail, NavigationRailItem } from "./ui/navigation-rail";
207
+ export type {
208
+ CircularProgressProps,
209
+ LinearProgressProps,
210
+ ProgressIndicatorProps,
211
+ } from "./ui/progress-indicator";
212
+ export { ProgressIndicator } from "./ui/progress-indicator";
213
+ // RadioButton — MD3 Expressive radio button
214
+ export type {
215
+ RadioButtonColors,
216
+ RadioButtonProps,
217
+ RadioGroupProps,
218
+ } from "./ui/radio-button";
219
+ export { RadioButton, RadioGroup } from "./ui/radio-button";
220
+ export type {
221
+ RippleOrigin,
222
+ RippleProps,
223
+ UseRippleStateOptions,
224
+ } from "./ui/ripple";
225
+ export { Ripple, useRipple, useRippleState } from "./ui/ripple";
226
+ // ScrollArea
227
+ export type {
228
+ ScrollAreaOrientation,
229
+ ScrollAreaProps,
230
+ ScrollAreaType,
231
+ } from "./ui/scroll-area";
232
+ export {
233
+ ScrollArea,
234
+ ScrollAreaScrollbar,
235
+ } from "./ui/scroll-area";
236
+ // Search — MD3 Expressive Search component
237
+ export type {
238
+ SearchProps,
239
+ SearchStyleType,
240
+ SearchVariant,
241
+ } from "./ui/search";
242
+ export {
243
+ SEARCH_BAR_EXPAND_SPRING,
244
+ SEARCH_COLORS,
245
+ SEARCH_DOCKED_REVEAL_SPRING,
246
+ SEARCH_FULLSCREEN_SPRING,
247
+ SEARCH_TYPOGRAPHY,
248
+ Search,
249
+ SearchBar,
250
+ SearchTokens,
251
+ SearchViewDocked,
252
+ SearchViewFullScreen,
253
+ useSearchKeyboard,
254
+ } from "./ui/search";
255
+ // Slider — MD3 Expressive
256
+ export type {
257
+ RangeSliderProps,
258
+ SliderOrientation,
259
+ SliderProps,
260
+ SliderTrackSize,
261
+ SliderVariant,
262
+ } from "./ui/slider";
263
+ export { RangeSlider, Slider, SliderColors, SliderTokens } from "./ui/slider";
264
+ // Snackbar — MD3 Expressive imperative toast system
265
+ export type {
266
+ SnackbarData,
267
+ SnackbarDuration,
268
+ SnackbarHostProps,
269
+ SnackbarProps,
270
+ SnackbarResult,
271
+ SnackbarVisuals,
272
+ UseSnackbarStateReturn,
273
+ } from "./ui/snackbar";
274
+ export {
275
+ Snackbar,
276
+ SnackbarHost,
277
+ SnackbarProvider,
278
+ useSnackbar,
279
+ useSnackbarState,
280
+ } from "./ui/snackbar";
281
+ // Switch — MD3 Expressive toggle
282
+ export type { SwitchProps } from "./ui/switch";
283
+ export { Switch, SwitchColors, SwitchTokens } from "./ui/switch";
284
+ export { Text, type TextProps } from "./ui/Text";
285
+ // Tabs — MD3 Expressive navigation tabs
286
+ export type {
287
+ TabProps,
288
+ TabsContentProps,
289
+ TabsListProps,
290
+ TabsProps,
291
+ TabsVariant,
292
+ } from "./ui/tabs";
293
+ export {
294
+ Tab,
295
+ Tabs,
296
+ TabsColors,
297
+ TabsContent,
298
+ TabsList,
299
+ TabsTokens,
300
+ } from "./ui/tabs";
301
+ // TextField — MD3 Expressive
302
+ export type {
303
+ TextFieldHandle,
304
+ TextFieldInputType,
305
+ TextFieldProps,
306
+ TextFieldTrailingIconMode,
307
+ TextFieldVariant,
308
+ } from "./ui/text-field";
309
+ export { TextField } from "./ui/text-field";
310
+ export type { MD3ThemeProviderProps } from "./ui/theme-provider";
311
+ export { MD3ThemeProvider, useTheme, useThemeMode } from "./ui/theme-provider";
312
+ export type { TableOfContentsProps, ToCItem } from "./ui/toc";
313
+ export { TableOfContents } from "./ui/toc";
314
+ // Tooltip — MD3 Expressive
315
+ export type {
316
+ CaretConfig,
317
+ PlainTooltipProps,
318
+ RichTooltipProps,
319
+ TooltipBoxProps,
320
+ TooltipPlacement,
321
+ TooltipState,
322
+ TooltipStateConfig,
323
+ TooltipTrigger,
324
+ } from "./ui/tooltip";
325
+ export {
326
+ PlainTooltip,
327
+ RichTooltip,
328
+ TooltipBox,
329
+ TooltipCaretShape,
330
+ TooltipTokens,
331
+ useTooltipPosition,
332
+ useTooltipState,
333
+ } from "./ui/tooltip";
334
+ export type {
335
+ TextStyle,
336
+ TypeScaleTokensType,
337
+ TypographyProviderProps,
338
+ } from "./ui/typography";
339
+ // Typography - MD3 Expressive
340
+ export {
341
+ MD3_EXPRESSIVE_FONT_VARIATION,
342
+ TypeScaleTokens,
343
+ Typography,
344
+ TypographyContext,
345
+ TypographyKeyTokens,
346
+ TypographyProvider,
347
+ TypographyTokens,
348
+ useTypography,
349
+ } from "./ui/typography";
@@ -0,0 +1,82 @@
1
+ /**
2
+ * MaterialSymbolsPreconnect
3
+ *
4
+ * Inject preconnect resource hints cho Google Fonts CDN và <head>.
5
+ * Đặt component này CÀNG SỚM CÀNG TỐT trong app tree, lý tưởng là
6
+ * ngay trong <head> hoặc root layout.
7
+ *
8
+ * WHY THIS MATTERS:
9
+ * Nếu @import url() nằm trong CSS file, browser phải:
10
+ * 1. Parse HTML -> download JS bundle -> execute CSS -> gặp @import -> mới bắt đầu connect Google Fonts
11
+ * Preconnect hints cho phép browser bắt đầu TCP handshake + TLS ngay từ bước 1,
12
+ * tiết kiệm 100-500ms connection time tùy network.
13
+ *
14
+ * USAGE:
15
+ * ```tsx
16
+ * // app/layout.tsx (Next.js) hoặc index.html equivalent
17
+ * import { MaterialSymbolsPreconnect } from '@bug-on/md3-react';
18
+ *
19
+ * export default function RootLayout({ children }) {
20
+ * return (
21
+ * <html>
22
+ * <head>
23
+ * <MaterialSymbolsPreconnect />
24
+ * </head>
25
+ * <body>{children}</body>
26
+ * </html>
27
+ * );
28
+ * }
29
+ * ```
30
+ *
31
+ * NOTE: Chỉ dùng component này với CDN mode.
32
+ * Với self-hosted fonts thì không cần preconnect đến external origin.
33
+ */
34
+ export interface MaterialSymbolsPreconnectProps {
35
+ /**
36
+ * Mảng các biến thể font Material Symbols cần tải.
37
+ * Chỉ nên chọn các biến thể mà ứng dụng thực sự sử dụng để tiết kiệm băng thông.
38
+ * @default ["outlined"]
39
+ */
40
+ variants?: Array<"outlined" | "rounded" | "sharp">;
41
+ }
42
+
43
+ export function MaterialSymbolsPreconnect({
44
+ variants = ["outlined"],
45
+ }: MaterialSymbolsPreconnectProps) {
46
+ return (
47
+ <>
48
+ {/* Preconnect cho CSS stylesheet */}
49
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
50
+ {/* Preconnect cho font files — crossorigin bắt buộc vì font download là CORS request */}
51
+ <link
52
+ rel="preconnect"
53
+ href="https://fonts.gstatic.com"
54
+ crossOrigin="anonymous"
55
+ />
56
+ {/* Load các biến thể được chọn.
57
+ Sử dụng display=swap để tối ưu hóa hiệu năng (Lighthouse/Next.js recommendation).
58
+ Lưu ý: Có thể gây ra hiện tượng chớp chữ (ligature flicker) trong tích tắc. */}
59
+ {variants.includes("outlined") && (
60
+ <link
61
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap"
62
+ rel="stylesheet"
63
+ precedence="default"
64
+ />
65
+ )}
66
+ {variants.includes("rounded") && (
67
+ <link
68
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap"
69
+ rel="stylesheet"
70
+ precedence="default"
71
+ />
72
+ )}
73
+ {variants.includes("sharp") && (
74
+ <link
75
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap"
76
+ rel="stylesheet"
77
+ precedence="default"
78
+ />
79
+ )}
80
+ </>
81
+ );
82
+ }
@@ -0,0 +1,180 @@
1
+ import {
2
+ argbFromHex,
3
+ hexFromArgb,
4
+ themeFromSourceColor,
5
+ } from "@material/material-color-utilities";
6
+
7
+ export type ThemeMode = "light" | "dark";
8
+
9
+ export interface MD3ColorScheme {
10
+ primary: string;
11
+ onPrimary: string;
12
+ primaryContainer: string;
13
+ onPrimaryContainer: string;
14
+ inversePrimary: string;
15
+ primaryFixed: string;
16
+ primaryFixedDim: string;
17
+ onPrimaryFixed: string;
18
+ onPrimaryFixedVariant: string;
19
+
20
+ secondary: string;
21
+ onSecondary: string;
22
+ secondaryContainer: string;
23
+ onSecondaryContainer: string;
24
+ secondaryFixed: string;
25
+ secondaryFixedDim: string;
26
+ onSecondaryFixed: string;
27
+ onSecondaryFixedVariant: string;
28
+
29
+ tertiary: string;
30
+ onTertiary: string;
31
+ tertiaryContainer: string;
32
+ onTertiaryContainer: string;
33
+ tertiaryFixed: string;
34
+ tertiaryFixedDim: string;
35
+ onTertiaryFixed: string;
36
+ onTertiaryFixedVariant: string;
37
+
38
+ error: string;
39
+ onError: string;
40
+ errorContainer: string;
41
+ onErrorContainer: string;
42
+
43
+ surface: string;
44
+ onSurface: string;
45
+ surfaceVariant: string;
46
+ onSurfaceVariant: string;
47
+ surfaceTint: string;
48
+ surfaceContainerLowest: string;
49
+ surfaceContainerLow: string;
50
+ surfaceContainer: string;
51
+ surfaceContainerHigh: string;
52
+ surfaceContainerHighest: string;
53
+
54
+ inverseSurface: string;
55
+ inverseOnSurface: string;
56
+
57
+ background: string;
58
+ onBackground: string;
59
+
60
+ outline: string;
61
+ outlineVariant: string;
62
+
63
+ shadow: string;
64
+ scrim: string;
65
+ }
66
+
67
+ /**
68
+ * Generate a complete MD3 color scheme from a source color hex string.
69
+ * Uses the HCT color space algorithm — same as Material You on Android.
70
+ */
71
+ export function generateM3Theme(
72
+ sourceColorHex: string,
73
+ mode: ThemeMode = "light",
74
+ ): MD3ColorScheme {
75
+ const sourceColor = argbFromHex(sourceColorHex);
76
+ const theme = themeFromSourceColor(sourceColor);
77
+
78
+ const scheme = mode === "light" ? theme.schemes.light : theme.schemes.dark;
79
+ const palettes = theme.palettes;
80
+
81
+ const tone = (palette: (typeof palettes)[keyof typeof palettes], t: number) =>
82
+ hexFromArgb(palette.tone(t));
83
+
84
+ return {
85
+ primary: hexFromArgb(scheme.primary),
86
+ onPrimary: hexFromArgb(scheme.onPrimary),
87
+ primaryContainer: hexFromArgb(scheme.primaryContainer),
88
+ onPrimaryContainer: hexFromArgb(scheme.onPrimaryContainer),
89
+ inversePrimary: hexFromArgb(scheme.inversePrimary),
90
+ primaryFixed: tone(palettes.primary, 90),
91
+ primaryFixedDim: tone(palettes.primary, 80),
92
+ onPrimaryFixed: tone(palettes.primary, 10),
93
+ onPrimaryFixedVariant: tone(palettes.primary, 30),
94
+
95
+ secondary: hexFromArgb(scheme.secondary),
96
+ onSecondary: hexFromArgb(scheme.onSecondary),
97
+ secondaryContainer: hexFromArgb(scheme.secondaryContainer),
98
+ onSecondaryContainer: hexFromArgb(scheme.onSecondaryContainer),
99
+ secondaryFixed: tone(palettes.secondary, 90),
100
+ secondaryFixedDim: tone(palettes.secondary, 80),
101
+ onSecondaryFixed: tone(palettes.secondary, 10),
102
+ onSecondaryFixedVariant: tone(palettes.secondary, 30),
103
+
104
+ tertiary: hexFromArgb(scheme.tertiary),
105
+ onTertiary: hexFromArgb(scheme.onTertiary),
106
+ tertiaryContainer: hexFromArgb(scheme.tertiaryContainer),
107
+ onTertiaryContainer: hexFromArgb(scheme.onTertiaryContainer),
108
+ tertiaryFixed: tone(palettes.tertiary, 90),
109
+ tertiaryFixedDim: tone(palettes.tertiary, 80),
110
+ onTertiaryFixed: tone(palettes.tertiary, 10),
111
+ onTertiaryFixedVariant: tone(palettes.tertiary, 30),
112
+
113
+ error: hexFromArgb(scheme.error),
114
+ onError: hexFromArgb(scheme.onError),
115
+ errorContainer: hexFromArgb(scheme.errorContainer),
116
+ onErrorContainer: hexFromArgb(scheme.onErrorContainer),
117
+
118
+ surface: hexFromArgb(scheme.surface),
119
+ onSurface: hexFromArgb(scheme.onSurface),
120
+ surfaceVariant: hexFromArgb(scheme.surfaceVariant),
121
+ onSurfaceVariant: hexFromArgb(scheme.onSurfaceVariant),
122
+ surfaceTint: hexFromArgb(scheme.primary),
123
+ // Surface container roles from neutral palette tones
124
+ surfaceContainerLowest:
125
+ mode === "light"
126
+ ? tone(palettes.neutral, 100)
127
+ : tone(palettes.neutral, 4),
128
+ surfaceContainerLow:
129
+ mode === "light"
130
+ ? tone(palettes.neutral, 96)
131
+ : tone(palettes.neutral, 10),
132
+ surfaceContainer:
133
+ mode === "light"
134
+ ? tone(palettes.neutral, 94)
135
+ : tone(palettes.neutral, 12),
136
+ surfaceContainerHigh:
137
+ mode === "light"
138
+ ? tone(palettes.neutral, 92)
139
+ : tone(palettes.neutral, 17),
140
+ surfaceContainerHighest:
141
+ mode === "light"
142
+ ? tone(palettes.neutral, 90)
143
+ : tone(palettes.neutral, 22),
144
+
145
+ inverseSurface: hexFromArgb(scheme.inverseSurface),
146
+ inverseOnSurface: hexFromArgb(scheme.inverseOnSurface),
147
+
148
+ background: hexFromArgb(scheme.background),
149
+ onBackground: hexFromArgb(scheme.onBackground),
150
+
151
+ outline: hexFromArgb(scheme.outline),
152
+ outlineVariant: hexFromArgb(scheme.outlineVariant),
153
+
154
+ shadow: hexFromArgb(scheme.shadow),
155
+ scrim: hexFromArgb(scheme.scrim),
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Apply an MD3 dynamic color scheme to the document root as CSS custom properties.
161
+ * Sets both `--md-sys-color-*` tokens (used by components) and
162
+ * `--color-m3-*` tokens (used by Tailwind arbitrary values in apps).
163
+ *
164
+ * Also sets `data-theme` attribute for dark mode CSS selectors.
165
+ */
166
+ export function applyTheme(
167
+ sourceColorHex: string,
168
+ mode: ThemeMode = "light",
169
+ root: HTMLElement = document.documentElement,
170
+ ): void {
171
+ const colors = generateM3Theme(sourceColorHex, mode);
172
+
173
+ for (const [key, value] of Object.entries(colors)) {
174
+ const kebabKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
175
+ root.style.setProperty(`--md-sys-color-${kebabKey}`, value);
176
+ root.style.setProperty(`--color-m3-${kebabKey}`, value);
177
+ }
178
+
179
+ root.setAttribute("data-theme", mode);
180
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,59 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { Button } from "../ui/button";
5
+
6
+ describe("Button Loading State", () => {
7
+ it("does not render LoadingIndicator by default", () => {
8
+ render(<Button>Submit</Button>);
9
+ expect(screen.queryByRole("progressbar")).not.toBeInTheDocument();
10
+ });
11
+
12
+ it("renders LoadingIndicator when loading=true", () => {
13
+ render(<Button loading>Submit</Button>);
14
+ const progressbar = screen.getByRole("progressbar");
15
+ expect(progressbar).toBeInTheDocument();
16
+ expect(progressbar).toHaveAttribute("aria-label", "Loading");
17
+ });
18
+
19
+ it("applies aria-busy and aria-disabled when loading", () => {
20
+ render(<Button loading>Submit</Button>);
21
+ const button = screen.getByRole("button", { name: "Submit" });
22
+ expect(button).toHaveAttribute("aria-busy", "true");
23
+ expect(button).toHaveAttribute("aria-disabled", "true");
24
+ });
25
+
26
+ it("prevents interactions when loading", async () => {
27
+ const handleClick = vi.fn();
28
+ render(
29
+ <Button loading onClick={handleClick}>
30
+ Submit
31
+ </Button>,
32
+ );
33
+
34
+ const button = screen.getByRole("button", { name: "Submit" });
35
+ await userEvent.click(button);
36
+
37
+ expect(handleClick).not.toHaveBeenCalled();
38
+ });
39
+
40
+ it("replaces leading icon with loading indicator", () => {
41
+ const FakeIcon = <svg data-testid="fake-icon" />;
42
+
43
+ const { rerender } = render(
44
+ <Button icon={FakeIcon} iconPosition="leading">
45
+ Submit
46
+ </Button>,
47
+ );
48
+ expect(screen.getByTestId("fake-icon")).toBeInTheDocument();
49
+ expect(screen.queryByRole("progressbar")).not.toBeInTheDocument();
50
+
51
+ rerender(
52
+ <Button loading icon={FakeIcon} iconPosition="leading">
53
+ Submit
54
+ </Button>,
55
+ );
56
+ expect(screen.queryByTestId("fake-icon")).not.toBeInTheDocument();
57
+ expect(screen.getByRole("progressbar")).toBeInTheDocument();
58
+ });
59
+ });