@bug-on/md3-react 2.0.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/.turbo/turbo-build.log +42 -0
  2. package/CHANGELOG.md +69 -0
  3. package/dist/index.css +178 -0
  4. package/dist/index.css.d.ts +2 -0
  5. package/dist/index.d.mts +6135 -0
  6. package/dist/index.d.ts +6135 -71
  7. package/dist/index.js +1688 -631
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +1600 -564
  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/plugin.d.mts +1 -0
  14. package/dist/plugin.d.ts +1 -0
  15. package/dist/plugin.js +13 -0
  16. package/dist/plugin.js.map +1 -0
  17. package/dist/plugin.mjs +3 -0
  18. package/dist/plugin.mjs.map +1 -0
  19. package/dist/typography.css.d.ts +2 -0
  20. package/package.json +28 -19
  21. package/scripts/copy-assets.js +115 -0
  22. package/src/assets/fonts/GoogleSansFlex-VariableFont.woff2 +0 -0
  23. package/src/assets/fonts/MaterialSymbolsOutlined-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  24. package/src/assets/fonts/MaterialSymbolsRounded-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  25. package/src/assets/fonts/MaterialSymbolsSharp-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  26. package/src/assets/loading-indicator.svg +19 -0
  27. package/src/assets/material-symbols-cdn.css +65 -0
  28. package/src/assets/material-symbols-self-hosted.css +90 -0
  29. package/src/css.d.ts +20 -0
  30. package/src/hooks/useClickOutside.ts +37 -0
  31. package/src/hooks/useMediaQuery.ts +28 -0
  32. package/src/hooks/useRipple.ts +88 -0
  33. package/src/index.css +23 -0
  34. package/src/index.ts +349 -0
  35. package/src/lib/material-symbols-preconnect.tsx +82 -0
  36. package/src/lib/theme-utils.ts +195 -0
  37. package/src/lib/utils.ts +6 -0
  38. package/src/plugin.ts +12 -0
  39. package/src/test/button.test.tsx +59 -0
  40. package/src/test/icon.test.tsx +91 -0
  41. package/src/test/loading-indicator.test.tsx +128 -0
  42. package/src/test/progress-indicator.test.tsx +306 -0
  43. package/src/test/setup.ts +80 -0
  44. package/src/test/typography.test.tsx +206 -0
  45. package/src/types/index.ts +7 -0
  46. package/src/types/md3.ts +31 -0
  47. package/src/ui/Text.tsx +60 -0
  48. package/src/ui/__snapshots__/divider.test.tsx.snap +63 -0
  49. package/src/ui/app-bar/app-bar-column.tsx +99 -0
  50. package/src/ui/app-bar/app-bar-item-button.tsx +71 -0
  51. package/src/ui/app-bar/app-bar-items.test.tsx +89 -0
  52. package/src/ui/app-bar/app-bar-overflow-indicator.tsx +108 -0
  53. package/src/ui/app-bar/app-bar-row.tsx +104 -0
  54. package/src/ui/app-bar/app-bar.test.tsx +87 -0
  55. package/src/ui/app-bar/app-bar.tokens.ts +223 -0
  56. package/src/ui/app-bar/app-bar.types.ts +441 -0
  57. package/src/ui/app-bar/bottom-app-bar.test.tsx +42 -0
  58. package/src/ui/app-bar/bottom-app-bar.tsx +84 -0
  59. package/src/ui/app-bar/docked-toolbar.test.tsx +34 -0
  60. package/src/ui/app-bar/docked-toolbar.tsx +54 -0
  61. package/src/ui/app-bar/flexible-app-bar.test.tsx +75 -0
  62. package/src/ui/app-bar/hooks/use-app-bar-scroll.ts +110 -0
  63. package/src/ui/app-bar/hooks/use-flexible-app-bar.ts +123 -0
  64. package/{dist/ui/app-bar/index.d.ts → src/ui/app-bar/index.ts} +35 -2
  65. package/src/ui/app-bar/large-flexible-app-bar.tsx +165 -0
  66. package/src/ui/app-bar/medium-flexible-app-bar.tsx +167 -0
  67. package/src/ui/app-bar/search-app-bar.test.tsx +49 -0
  68. package/src/ui/app-bar/search-app-bar.tsx +176 -0
  69. package/src/ui/app-bar/search-view.tsx +227 -0
  70. package/src/ui/app-bar/small-app-bar.test.tsx +48 -0
  71. package/src/ui/app-bar/small-app-bar.tsx +203 -0
  72. package/src/ui/badge.test.tsx +345 -0
  73. package/src/ui/badge.tsx +282 -0
  74. package/src/ui/button-group.test.tsx +71 -0
  75. package/src/ui/button-group.tsx +350 -0
  76. package/src/ui/button.test.tsx +306 -0
  77. package/src/ui/button.tsx +665 -0
  78. package/src/ui/card.test.tsx +187 -0
  79. package/src/ui/card.tsx +259 -0
  80. package/src/ui/checkbox.test.tsx +423 -0
  81. package/src/ui/checkbox.tsx +525 -0
  82. package/src/ui/chip.test.tsx +292 -0
  83. package/src/ui/chip.tsx +548 -0
  84. package/src/ui/code-block.tsx +219 -0
  85. package/src/ui/dialog.test.tsx +300 -0
  86. package/src/ui/dialog.tsx +384 -0
  87. package/src/ui/divider.test.tsx +314 -0
  88. package/src/ui/divider.tsx +412 -0
  89. package/src/ui/drawer.tsx +240 -0
  90. package/src/ui/fab-menu.test.tsx +494 -0
  91. package/src/ui/fab-menu.tsx +739 -0
  92. package/src/ui/fab.test.tsx +232 -0
  93. package/src/ui/fab.tsx +505 -0
  94. package/src/ui/icon-button.test.tsx +515 -0
  95. package/src/ui/icon-button.tsx +525 -0
  96. package/src/ui/icon.test.tsx +197 -0
  97. package/src/ui/icon.tsx +179 -0
  98. package/src/ui/loading-indicator.test.tsx +73 -0
  99. package/src/ui/loading-indicator.tsx +312 -0
  100. package/src/ui/menu/context-menu.tsx +275 -0
  101. package/src/ui/menu/index.ts +77 -0
  102. package/src/ui/menu/menu-animations.ts +102 -0
  103. package/src/ui/menu/menu-context.tsx +99 -0
  104. package/src/ui/menu/menu-divider.tsx +47 -0
  105. package/src/ui/menu/menu-group.tsx +200 -0
  106. package/src/ui/menu/menu-item.tsx +294 -0
  107. package/src/ui/menu/menu-tokens.ts +208 -0
  108. package/src/ui/menu/menu-types.ts +313 -0
  109. package/src/ui/menu/menu.test.tsx +624 -0
  110. package/src/ui/menu/menu.tsx +289 -0
  111. package/src/ui/menu/sub-menu.tsx +223 -0
  112. package/src/ui/menu/vertical-menu.tsx +382 -0
  113. package/src/ui/navigation-rail.test.tsx +404 -0
  114. package/src/ui/navigation-rail.tsx +607 -0
  115. package/src/ui/progress-indicator/circular.tsx +248 -0
  116. package/src/ui/progress-indicator/hooks.ts +51 -0
  117. package/{dist/ui/progress-indicator/index.d.ts → src/ui/progress-indicator/index.tsx} +20 -2
  118. package/src/ui/progress-indicator/linear-flat.tsx +83 -0
  119. package/src/ui/progress-indicator/linear-wavy.tsx +243 -0
  120. package/src/ui/progress-indicator/linear.tsx +143 -0
  121. package/src/ui/progress-indicator/types.ts +158 -0
  122. package/src/ui/progress-indicator/utils.ts +73 -0
  123. package/src/ui/radio-button.test.tsx +407 -0
  124. package/src/ui/radio-button.tsx +551 -0
  125. package/src/ui/ripple.test.tsx +72 -0
  126. package/src/ui/ripple.tsx +234 -0
  127. package/src/ui/scroll-area.test.tsx +58 -0
  128. package/src/ui/scroll-area.tsx +139 -0
  129. package/src/ui/search/animated-placeholder.tsx +145 -0
  130. package/src/ui/search/hooks/use-search-keyboard.test.ts +202 -0
  131. package/src/ui/search/hooks/use-search-keyboard.ts +104 -0
  132. package/src/ui/search/hooks/use-search-view-focus.test.ts +96 -0
  133. package/src/ui/search/hooks/use-search-view-focus.ts +24 -0
  134. package/src/ui/search/index.ts +44 -0
  135. package/src/ui/search/search-bar.tsx +220 -0
  136. package/src/ui/search/search-context.tsx +42 -0
  137. package/src/ui/search/search-view-docked.tsx +194 -0
  138. package/src/ui/search/search-view-fullscreen.tsx +247 -0
  139. package/src/ui/search/search.test.tsx +233 -0
  140. package/src/ui/search/search.tokens.ts +134 -0
  141. package/src/ui/search/search.tsx +131 -0
  142. package/src/ui/search/search.types.ts +154 -0
  143. package/src/ui/search/trailing-action.tsx +49 -0
  144. package/src/ui/shared/constants.ts +135 -0
  145. package/{dist/ui/shared/touch-target.d.ts → src/ui/shared/touch-target.tsx} +13 -1
  146. package/src/ui/slider/hooks/useSliderMath.ts +195 -0
  147. package/{dist/ui/slider/index.d.ts → src/ui/slider/index.ts} +12 -1
  148. package/src/ui/slider/range-slider.tsx +561 -0
  149. package/src/ui/slider/slider-thumb.tsx +379 -0
  150. package/src/ui/slider/slider-track.tsx +912 -0
  151. package/src/ui/slider/slider.tokens.ts +189 -0
  152. package/src/ui/slider/slider.tsx +259 -0
  153. package/src/ui/slider/slider.types.ts +288 -0
  154. package/src/ui/snackbar/index.ts +20 -0
  155. package/src/ui/snackbar/snackbar.test.tsx +338 -0
  156. package/src/ui/snackbar/snackbar.tsx +476 -0
  157. package/{dist/ui/switch/index.d.ts → src/ui/switch/index.ts} +1 -0
  158. package/src/ui/switch/switch.stories.tsx +309 -0
  159. package/src/ui/switch/switch.test.tsx +243 -0
  160. package/src/ui/switch/switch.tokens.ts +89 -0
  161. package/src/ui/switch/switch.tsx +504 -0
  162. package/src/ui/switch/switch.types.ts +62 -0
  163. package/{dist/ui/tabs/index.d.ts → src/ui/tabs/index.ts} +8 -1
  164. package/src/ui/tabs/tab.tsx +407 -0
  165. package/src/ui/tabs/tabs-content.tsx +89 -0
  166. package/src/ui/tabs/tabs-list.tsx +146 -0
  167. package/src/ui/tabs/tabs.test.tsx +290 -0
  168. package/src/ui/tabs/tabs.tokens.ts +121 -0
  169. package/src/ui/tabs/tabs.tsx +229 -0
  170. package/src/ui/tabs/tabs.types.ts +185 -0
  171. package/{dist/ui/text-field/index.d.ts → src/ui/text-field/index.ts} +8 -1
  172. package/src/ui/text-field/subcomponents/active-indicator.tsx +67 -0
  173. package/src/ui/text-field/subcomponents/floating-label.tsx +161 -0
  174. package/src/ui/text-field/subcomponents/leading-icon.tsx +46 -0
  175. package/src/ui/text-field/subcomponents/outline-container.tsx +170 -0
  176. package/src/ui/text-field/subcomponents/prefix-suffix.tsx +59 -0
  177. package/src/ui/text-field/subcomponents/supporting-text.tsx +145 -0
  178. package/src/ui/text-field/subcomponents/trailing-icon.tsx +199 -0
  179. package/src/ui/text-field/text-field.test.tsx +454 -0
  180. package/src/ui/text-field/text-field.tokens.ts +104 -0
  181. package/src/ui/text-field/text-field.tsx +548 -0
  182. package/src/ui/text-field/text-field.types.ts +180 -0
  183. package/src/ui/theme-provider/index.tsx +215 -0
  184. package/src/ui/toc.test.tsx +108 -0
  185. package/src/ui/toc.tsx +172 -0
  186. package/src/ui/tooltip/plain-tooltip.tsx +63 -0
  187. package/src/ui/tooltip/rich-tooltip.tsx +94 -0
  188. package/src/ui/tooltip/tooltip-box.tsx +266 -0
  189. package/src/ui/tooltip/tooltip-caret-shape.tsx +68 -0
  190. package/src/ui/tooltip/tooltip.tokens.ts +26 -0
  191. package/src/ui/tooltip/tooltip.types.ts +70 -0
  192. package/src/ui/tooltip/use-tooltip-position.ts +208 -0
  193. package/src/ui/tooltip/use-tooltip-state.ts +41 -0
  194. package/src/ui/typography/__tests__/typography.test.tsx +170 -0
  195. package/{dist/ui/typography/index.d.ts → src/ui/typography/index.ts} +21 -3
  196. package/src/ui/typography/type-scale-tokens.ts +205 -0
  197. package/src/ui/typography/typography-key-tokens.ts +43 -0
  198. package/src/ui/typography/typography-tokens.ts +360 -0
  199. package/src/ui/typography/typography.css +22 -0
  200. package/src/ui/typography/typography.tsx +559 -0
  201. package/test-render.tsx +4 -0
  202. package/test-shadow.html +26 -0
  203. package/test_output.txt +164 -0
  204. package/test_output_v2.txt +5 -0
  205. package/tsconfig.build.json +10 -0
  206. package/tsconfig.json +18 -0
  207. package/tsup.config.ts +20 -0
  208. package/vitest.config.ts +11 -0
  209. package/dist/hooks/useClickOutside.d.ts +0 -8
  210. package/dist/hooks/useMediaQuery.d.ts +0 -11
  211. package/dist/hooks/useRipple.d.ts +0 -26
  212. package/dist/lib/material-symbols-preconnect.d.ts +0 -42
  213. package/dist/lib/theme-utils.d.ts +0 -63
  214. package/dist/lib/utils.d.ts +0 -2
  215. package/dist/types/index.d.ts +0 -1
  216. package/dist/types/md3.d.ts +0 -14
  217. package/dist/ui/app-bar/app-bar-column.d.ts +0 -28
  218. package/dist/ui/app-bar/app-bar-item-button.d.ts +0 -16
  219. package/dist/ui/app-bar/app-bar-overflow-indicator.d.ts +0 -18
  220. package/dist/ui/app-bar/app-bar-row.d.ts +0 -36
  221. package/dist/ui/app-bar/app-bar.tokens.d.ts +0 -184
  222. package/dist/ui/app-bar/app-bar.types.d.ts +0 -392
  223. package/dist/ui/app-bar/bottom-app-bar.d.ts +0 -31
  224. package/dist/ui/app-bar/docked-toolbar.d.ts +0 -25
  225. package/dist/ui/app-bar/hooks/use-app-bar-scroll.d.ts +0 -42
  226. package/dist/ui/app-bar/hooks/use-flexible-app-bar.d.ts +0 -37
  227. package/dist/ui/app-bar/large-flexible-app-bar.d.ts +0 -26
  228. package/dist/ui/app-bar/medium-flexible-app-bar.d.ts +0 -28
  229. package/dist/ui/app-bar/search-app-bar.d.ts +0 -43
  230. package/dist/ui/app-bar/search-view.d.ts +0 -54
  231. package/dist/ui/app-bar/small-app-bar.d.ts +0 -37
  232. package/dist/ui/badge.d.ts +0 -125
  233. package/dist/ui/button-group.d.ts +0 -59
  234. package/dist/ui/button.d.ts +0 -148
  235. package/dist/ui/card.d.ts +0 -62
  236. package/dist/ui/checkbox.d.ts +0 -82
  237. package/dist/ui/chip.d.ts +0 -110
  238. package/dist/ui/code-block.d.ts +0 -14
  239. package/dist/ui/dialog.d.ts +0 -111
  240. package/dist/ui/divider.d.ts +0 -164
  241. package/dist/ui/drawer.d.ts +0 -39
  242. package/dist/ui/dropdown.d.ts +0 -29
  243. package/dist/ui/fab-menu.d.ts +0 -204
  244. package/dist/ui/fab.d.ts +0 -162
  245. package/dist/ui/icon-button.d.ts +0 -131
  246. package/dist/ui/icon.d.ts +0 -88
  247. package/dist/ui/loading-indicator.d.ts +0 -42
  248. package/dist/ui/navigation-rail.d.ts +0 -29
  249. package/dist/ui/progress-indicator/circular.d.ts +0 -3
  250. package/dist/ui/progress-indicator/hooks.d.ts +0 -3
  251. package/dist/ui/progress-indicator/linear-flat.d.ts +0 -10
  252. package/dist/ui/progress-indicator/linear-wavy.d.ts +0 -18
  253. package/dist/ui/progress-indicator/linear.d.ts +0 -3
  254. package/dist/ui/progress-indicator/types.d.ts +0 -151
  255. package/dist/ui/progress-indicator/utils.d.ts +0 -3
  256. package/dist/ui/radio-button.d.ts +0 -106
  257. package/dist/ui/ripple.d.ts +0 -126
  258. package/dist/ui/scroll-area.d.ts +0 -27
  259. package/dist/ui/search/animated-placeholder.d.ts +0 -54
  260. package/dist/ui/search/hooks/use-search-keyboard.d.ts +0 -32
  261. package/dist/ui/search/hooks/use-search-view-focus.d.ts +0 -6
  262. package/dist/ui/search/index.d.ts +0 -27
  263. package/dist/ui/search/search-bar.d.ts +0 -32
  264. package/dist/ui/search/search-context.d.ts +0 -24
  265. package/dist/ui/search/search-view-docked.d.ts +0 -25
  266. package/dist/ui/search/search-view-fullscreen.d.ts +0 -36
  267. package/dist/ui/search/search.d.ts +0 -50
  268. package/dist/ui/search/search.tokens.d.ts +0 -112
  269. package/dist/ui/search/search.types.d.ts +0 -131
  270. package/dist/ui/search/trailing-action.d.ts +0 -9
  271. package/dist/ui/shared/constants.d.ts +0 -86
  272. package/dist/ui/slider/hooks/useSliderMath.d.ts +0 -101
  273. package/dist/ui/slider/range-slider.d.ts +0 -47
  274. package/dist/ui/slider/slider-thumb.d.ts +0 -33
  275. package/dist/ui/slider/slider-track.d.ts +0 -25
  276. package/dist/ui/slider/slider.d.ts +0 -60
  277. package/dist/ui/slider/slider.tokens.d.ts +0 -151
  278. package/dist/ui/slider/slider.types.d.ts +0 -259
  279. package/dist/ui/snackbar/index.d.ts +0 -6
  280. package/dist/ui/snackbar/snackbar.d.ts +0 -197
  281. package/dist/ui/switch/switch.d.ts +0 -30
  282. package/dist/ui/switch/switch.stories.d.ts +0 -48
  283. package/dist/ui/switch/switch.tokens.d.ts +0 -67
  284. package/dist/ui/switch/switch.types.d.ts +0 -59
  285. package/dist/ui/tabs/tab.d.ts +0 -43
  286. package/dist/ui/tabs/tabs-content.d.ts +0 -36
  287. package/dist/ui/tabs/tabs-list.d.ts +0 -40
  288. package/dist/ui/tabs/tabs.d.ts +0 -60
  289. package/dist/ui/tabs/tabs.tokens.d.ts +0 -94
  290. package/dist/ui/tabs/tabs.types.d.ts +0 -172
  291. package/dist/ui/text-field/subcomponents/active-indicator.d.ts +0 -24
  292. package/dist/ui/text-field/subcomponents/floating-label.d.ts +0 -43
  293. package/dist/ui/text-field/subcomponents/leading-icon.d.ts +0 -23
  294. package/dist/ui/text-field/subcomponents/outline-container.d.ts +0 -42
  295. package/dist/ui/text-field/subcomponents/prefix-suffix.d.ts +0 -24
  296. package/dist/ui/text-field/subcomponents/supporting-text.d.ts +0 -37
  297. package/dist/ui/text-field/subcomponents/trailing-icon.d.ts +0 -41
  298. package/dist/ui/text-field/text-field.d.ts +0 -49
  299. package/dist/ui/text-field/text-field.tokens.d.ts +0 -76
  300. package/dist/ui/text-field/text-field.types.d.ts +0 -126
  301. package/dist/ui/theme-provider/index.d.ts +0 -48
  302. package/dist/ui/toc.d.ts +0 -80
  303. package/dist/ui/tooltip/plain-tooltip.d.ts +0 -2
  304. package/dist/ui/tooltip/rich-tooltip.d.ts +0 -2
  305. package/dist/ui/tooltip/tooltip-box.d.ts +0 -2
  306. package/dist/ui/tooltip/tooltip-caret-shape.d.ts +0 -9
  307. package/dist/ui/tooltip/tooltip.tokens.d.ts +0 -26
  308. package/dist/ui/tooltip/tooltip.types.d.ts +0 -56
  309. package/dist/ui/tooltip/use-tooltip-position.d.ts +0 -8
  310. package/dist/ui/tooltip/use-tooltip-state.d.ts +0 -2
  311. package/dist/ui/typography/type-scale-tokens.d.ts +0 -162
  312. package/dist/ui/typography/typography-key-tokens.d.ts +0 -40
  313. package/dist/ui/typography/typography-tokens.d.ts +0 -220
  314. package/dist/ui/typography/typography.d.ts +0 -265
  315. /package/{dist/hooks/index.d.ts → src/hooks/index.ts} +0 -0
  316. /package/{dist/ui/tooltip/index.d.ts → src/ui/tooltip/index.ts} +0 -0
@@ -0,0 +1,208 @@
1
+ import { useEffect, useLayoutEffect, useState } from "react";
2
+ import type { TooltipPlacement } from "./tooltip.types";
3
+
4
+ interface PositionState {
5
+ top: number;
6
+ left: number;
7
+ actualSide: "top" | "bottom" | "left" | "right";
8
+ }
9
+
10
+ type Side = "top" | "bottom" | "left" | "right";
11
+
12
+ const VIEWPORT_PADDING = 8;
13
+
14
+ const useIsomorphicLayoutEffect =
15
+ typeof window !== "undefined" ? useLayoutEffect : useEffect;
16
+
17
+ function resolveAutoPlacement(
18
+ spaceTop: number,
19
+ spaceBottom: number,
20
+ spaceLeft: number,
21
+ spaceRight: number,
22
+ tooltipWidth: number,
23
+ tooltipHeight: number,
24
+ spacing: number,
25
+ ): Side {
26
+ if (spaceTop >= tooltipHeight + spacing) return "top";
27
+ if (spaceBottom >= tooltipHeight + spacing) return "bottom";
28
+ if (spaceLeft >= tooltipWidth + spacing) return "left";
29
+ if (spaceRight >= tooltipWidth + spacing) return "right";
30
+ return "top";
31
+ }
32
+
33
+ function resolveActualSide(
34
+ targetPlacement: Side,
35
+ spaceTop: number,
36
+ spaceBottom: number,
37
+ spaceLeft: number,
38
+ spaceRight: number,
39
+ tooltipWidth: number,
40
+ tooltipHeight: number,
41
+ spacing: number,
42
+ ): Side {
43
+ const needsHeight = tooltipHeight + spacing;
44
+ const needsWidth = tooltipWidth + spacing;
45
+
46
+ if (targetPlacement === "top")
47
+ return spaceTop < needsHeight && spaceBottom >= needsHeight
48
+ ? "bottom"
49
+ : "top";
50
+ if (targetPlacement === "bottom")
51
+ return spaceBottom < needsHeight && spaceTop >= needsHeight
52
+ ? "top"
53
+ : "bottom";
54
+ if (targetPlacement === "left")
55
+ return spaceLeft < needsWidth && spaceRight >= needsWidth
56
+ ? "right"
57
+ : "left";
58
+ return spaceRight < needsWidth && spaceLeft >= needsWidth ? "left" : "right";
59
+ }
60
+
61
+ function calculateXY(
62
+ side: Side,
63
+ anchorRect: DOMRect,
64
+ tooltipWidth: number,
65
+ tooltipHeight: number,
66
+ spacing: number,
67
+ ): { top: number; left: number } {
68
+ const anchorCenterX = anchorRect.left + anchorRect.width / 2;
69
+ const anchorCenterY = anchorRect.top + anchorRect.height / 2;
70
+
71
+ if (side === "top")
72
+ return {
73
+ top: anchorRect.top - tooltipHeight - spacing,
74
+ left: anchorCenterX - tooltipWidth / 2,
75
+ };
76
+ if (side === "bottom")
77
+ return {
78
+ top: anchorRect.bottom + spacing,
79
+ left: anchorCenterX - tooltipWidth / 2,
80
+ };
81
+ if (side === "left")
82
+ return {
83
+ top: anchorCenterY - tooltipHeight / 2,
84
+ left: anchorRect.left - tooltipWidth - spacing,
85
+ };
86
+ return {
87
+ top: anchorCenterY - tooltipHeight / 2,
88
+ left: anchorRect.right + spacing,
89
+ };
90
+ }
91
+
92
+ function clampToViewport(
93
+ side: Side,
94
+ top: number,
95
+ left: number,
96
+ tooltipWidth: number,
97
+ tooltipHeight: number,
98
+ ): { top: number; left: number } {
99
+ const isVertical = side === "top" || side === "bottom";
100
+ const viewportWidth = window.innerWidth;
101
+ const viewportHeight = window.innerHeight;
102
+
103
+ if (isVertical) {
104
+ left = Math.max(
105
+ VIEWPORT_PADDING,
106
+ Math.min(left, viewportWidth - VIEWPORT_PADDING - tooltipWidth),
107
+ );
108
+ } else {
109
+ top = Math.max(
110
+ VIEWPORT_PADDING,
111
+ Math.min(top, viewportHeight - VIEWPORT_PADDING - tooltipHeight),
112
+ );
113
+ }
114
+
115
+ return { top, left };
116
+ }
117
+
118
+ export function useTooltipPosition(
119
+ anchorRef: React.RefObject<HTMLElement | null>,
120
+ tooltipRef: React.RefObject<HTMLElement | null>,
121
+ placement: TooltipPlacement,
122
+ spacing: number,
123
+ isVisible: boolean,
124
+ ): PositionState {
125
+ const [position, setPosition] = useState<PositionState>({
126
+ top: -9999,
127
+ left: -9999,
128
+ actualSide: placement === "auto" ? "top" : placement,
129
+ });
130
+
131
+ useIsomorphicLayoutEffect(() => {
132
+ if (!isVisible || !anchorRef.current || !tooltipRef.current) return;
133
+
134
+ const calculatePosition = () => {
135
+ const anchorEl = anchorRef.current;
136
+ const tooltipEl = tooltipRef.current;
137
+ if (!anchorEl || !tooltipEl) return;
138
+
139
+ const anchorRect = anchorEl.getBoundingClientRect();
140
+ const tooltipRect = tooltipEl.getBoundingClientRect();
141
+ const tooltipWidth = tooltipRect.width;
142
+ const tooltipHeight = tooltipRect.height;
143
+
144
+ const spaceTop = anchorRect.top;
145
+ const spaceBottom = window.innerHeight - anchorRect.bottom;
146
+ const spaceLeft = anchorRect.left;
147
+ const spaceRight = window.innerWidth - anchorRect.right;
148
+
149
+ const targetSide: Side =
150
+ placement === "auto"
151
+ ? resolveAutoPlacement(
152
+ spaceTop,
153
+ spaceBottom,
154
+ spaceLeft,
155
+ spaceRight,
156
+ tooltipWidth,
157
+ tooltipHeight,
158
+ spacing,
159
+ )
160
+ : placement;
161
+
162
+ const actualSide = resolveActualSide(
163
+ targetSide,
164
+ spaceTop,
165
+ spaceBottom,
166
+ spaceLeft,
167
+ spaceRight,
168
+ tooltipWidth,
169
+ tooltipHeight,
170
+ spacing,
171
+ );
172
+
173
+ const { top, left } = calculateXY(
174
+ actualSide,
175
+ anchorRect,
176
+ tooltipWidth,
177
+ tooltipHeight,
178
+ spacing,
179
+ );
180
+ const clamped = clampToViewport(
181
+ actualSide,
182
+ top,
183
+ left,
184
+ tooltipWidth,
185
+ tooltipHeight,
186
+ );
187
+
188
+ setPosition({ ...clamped, actualSide });
189
+ };
190
+
191
+ calculatePosition();
192
+
193
+ window.addEventListener("resize", calculatePosition);
194
+ window.addEventListener("scroll", calculatePosition, true);
195
+
196
+ const resizeObserver = new ResizeObserver(calculatePosition);
197
+ if (anchorRef.current) resizeObserver.observe(anchorRef.current);
198
+ if (tooltipRef.current) resizeObserver.observe(tooltipRef.current);
199
+
200
+ return () => {
201
+ window.removeEventListener("resize", calculatePosition);
202
+ window.removeEventListener("scroll", calculatePosition, true);
203
+ resizeObserver.disconnect();
204
+ };
205
+ }, [isVisible, placement, spacing, anchorRef, tooltipRef]);
206
+
207
+ return position;
208
+ }
@@ -0,0 +1,41 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import type { TooltipState, TooltipStateConfig } from "./tooltip.types";
3
+
4
+ const activeTooltipDismissals = new Set<() => void>();
5
+
6
+ export function useTooltipState(config?: TooltipStateConfig): TooltipState {
7
+ const {
8
+ initialVisible = false,
9
+ isPersistent = false,
10
+ duration = 1500,
11
+ } = config ?? {};
12
+
13
+ const [isVisible, setIsVisible] = useState(initialVisible);
14
+
15
+ const dismiss = useCallback(() => {
16
+ setIsVisible(false);
17
+ activeTooltipDismissals.delete(dismiss);
18
+ }, []);
19
+
20
+ const show = useCallback(() => {
21
+ for (const otherDismiss of activeTooltipDismissals) {
22
+ if (otherDismiss !== dismiss) otherDismiss();
23
+ }
24
+ setIsVisible(true);
25
+ activeTooltipDismissals.add(dismiss);
26
+ }, [dismiss]);
27
+
28
+ useEffect(() => {
29
+ if (!isVisible || isPersistent) return;
30
+ const timeoutId = setTimeout(dismiss, duration);
31
+ return () => clearTimeout(timeoutId);
32
+ }, [isVisible, isPersistent, duration, dismiss]);
33
+
34
+ useEffect(() => {
35
+ return () => {
36
+ activeTooltipDismissals.delete(dismiss);
37
+ };
38
+ }, [dismiss]);
39
+
40
+ return { isVisible, show, dismiss };
41
+ }
@@ -0,0 +1,170 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import { Typography, TypographyProvider, useTypography } from "../typography";
4
+ import { TypographyKeyTokens } from "../typography-key-tokens";
5
+ import {
6
+ DEFAULT_FONT_VARIATION_AXES,
7
+ serializeFontVariationAxes,
8
+ TypographyTokens,
9
+ } from "../typography-tokens";
10
+
11
+ describe("Typography", () => {
12
+ describe("serializeFontVariationAxes", () => {
13
+ it("should serialize a single axis correctly", () => {
14
+ expect(serializeFontVariationAxes({ ROND: 100 })).toBe('"ROND" 100');
15
+ });
16
+
17
+ it("should serialize multiple axes correctly", () => {
18
+ const result = serializeFontVariationAxes({ ROND: 50, wght: 700 });
19
+ expect(result).toContain('"ROND" 50');
20
+ expect(result).toContain('"wght" 700');
21
+ });
22
+
23
+ it("should skip undefined axis values", () => {
24
+ const result = serializeFontVariationAxes({ ROND: 100, wght: undefined });
25
+ expect(result).toBe('"ROND" 100');
26
+ });
27
+ });
28
+
29
+ describe("TypographyTokens", () => {
30
+ it("should initialize with default Google Sans Flex font", () => {
31
+ const tokens = new TypographyTokens();
32
+ expect(tokens.BodyLarge.fontFamily).toContain("Google Sans Flex");
33
+ // MD3 Expressive requires ROND: 100 for maximum roundness
34
+ expect(tokens.BodyLarge.fontVariationSettings).toContain('"ROND" 100');
35
+ });
36
+
37
+ it("should support a custom fontFamily override (options object)", () => {
38
+ const tokens = new TypographyTokens({ fontFamily: "Inter, sans-serif" });
39
+ expect(tokens.HeadlineLarge.fontFamily).toBe("Inter, sans-serif");
40
+ });
41
+
42
+ it("should support legacy string constructor for backward compatibility", () => {
43
+ const tokens = new TypographyTokens("Inter, sans-serif");
44
+ expect(tokens.HeadlineLarge.fontFamily).toBe("Inter, sans-serif");
45
+ });
46
+
47
+ it("should apply custom fontVariationAxes", () => {
48
+ const tokens = new TypographyTokens({ fontVariationAxes: { ROND: 0 } });
49
+ expect(tokens.BodyLarge.fontVariationSettings).toContain('"ROND" 0');
50
+ });
51
+
52
+ it("should merge custom axes on top of defaults (ROND stays 100 unless overridden)", () => {
53
+ // Only wght is overridden; ROND should remain at default (100)
54
+ expect(DEFAULT_FONT_VARIATION_AXES.ROND).toBe(100);
55
+ const tokens = new TypographyTokens({ fontVariationAxes: { wght: 600 } });
56
+ expect(tokens.BodyLarge.fontVariationSettings).toContain('"ROND" 100');
57
+ expect(tokens.BodyLarge.fontVariationSettings).toContain('"wght" 600');
58
+ });
59
+
60
+ it("should lazily compute and cache styles on first access", () => {
61
+ const tokens = new TypographyTokens();
62
+ const first = tokens.DisplayLarge;
63
+ const second = tokens.DisplayLarge;
64
+ // Same object reference — cached
65
+ expect(first).toBe(second);
66
+ });
67
+
68
+ it("should return frozen TextStyle objects", () => {
69
+ const tokens = new TypographyTokens();
70
+ expect(Object.isFrozen(tokens.BodyLarge)).toBe(true);
71
+ });
72
+ });
73
+
74
+ describe("Typography (Class)", () => {
75
+ it("should expose all 30 TextStyle getters", () => {
76
+ const typography = new Typography();
77
+ expect(typography.displayLarge).toBeDefined();
78
+ expect(typography.bodyMedium).toBeDefined();
79
+ expect(typography.labelSmallEmphasized).toBeDefined();
80
+
81
+ // Check token delegation — should match token value
82
+ const tokens = new TypographyTokens();
83
+ expect(typography.headlineMedium).toEqual(tokens.HeadlineMedium);
84
+ });
85
+
86
+ it("should map TypographyKeyTokens correctly using fromToken()", () => {
87
+ const typography = new Typography();
88
+ const style = typography.fromToken(TypographyKeyTokens.TitleMedium);
89
+ expect(style.fontSize).toBe("1.143rem");
90
+ expect(style.fontWeight).toBe(500);
91
+
92
+ const emphStyle = typography.fromToken(
93
+ TypographyKeyTokens.TitleMediumEmphasized,
94
+ );
95
+ expect(emphStyle.fontWeight).toBe(700);
96
+ });
97
+
98
+ it("should create an overridden copy without mutating the original via copy()", () => {
99
+ const original = new Typography();
100
+ const overridden = original.copy({
101
+ bodyLarge: { fontSize: "99rem", lineHeight: "120rem" },
102
+ });
103
+
104
+ // Original remains intact
105
+ expect(original.bodyLarge.fontSize).toBe("1.143rem");
106
+
107
+ // Overridden instance reflects custom values
108
+ expect(overridden.bodyLarge.fontSize).toBe("99rem");
109
+ expect(overridden.bodyLarge.lineHeight).toBe("120rem");
110
+
111
+ // Untouched properties remain referentially stable
112
+ expect(overridden.headlineSmall).toBe(original.headlineSmall);
113
+ });
114
+ });
115
+
116
+ describe("TypographyProvider & useTypography", () => {
117
+ it("should provide default Google Sans Flex typography when no props given", () => {
118
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
119
+ <TypographyProvider>{children}</TypographyProvider>
120
+ );
121
+ const { result } = renderHook(() => useTypography(), { wrapper });
122
+ expect(result.current.bodySmall.fontFamily).toContain("Google Sans Flex");
123
+ });
124
+
125
+ it("should respect fontFamily prop shorthand", () => {
126
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
127
+ <TypographyProvider fontFamily="'Roboto', sans-serif">
128
+ {children}
129
+ </TypographyProvider>
130
+ );
131
+ const { result } = renderHook(() => useTypography(), { wrapper });
132
+ expect(result.current.bodySmall.fontFamily).toBe("'Roboto', sans-serif");
133
+ });
134
+
135
+ it("should apply custom fontVariationAxes via prop", () => {
136
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
137
+ <TypographyProvider fontVariationAxes={{ ROND: 0 }}>
138
+ {children}
139
+ </TypographyProvider>
140
+ );
141
+ const { result } = renderHook(() => useTypography(), { wrapper });
142
+ expect(result.current.bodyLarge.fontVariationSettings).toContain(
143
+ '"ROND" 0',
144
+ );
145
+ });
146
+
147
+ it("should prioritize custom typography instance prop", () => {
148
+ const customTokens = new TypographyTokens({
149
+ fontFamily: "'Comic Sans MS'",
150
+ });
151
+ const customTypography = new Typography(customTokens);
152
+
153
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
154
+ <TypographyProvider typography={customTypography}>
155
+ {children}
156
+ </TypographyProvider>
157
+ );
158
+ const { result } = renderHook(() => useTypography(), { wrapper });
159
+ expect(result.current.displayLarge.fontFamily).toBe("'Comic Sans MS'");
160
+ });
161
+
162
+ it("should return the default Typography if used without a provider", () => {
163
+ const { result } = renderHook(() => useTypography());
164
+ expect(result.current).toBeInstanceOf(Typography);
165
+ expect(result.current.displaySmall.fontFamily).toContain(
166
+ "Google Sans Flex",
167
+ );
168
+ });
169
+ });
170
+ });
@@ -7,10 +7,28 @@
7
7
  *
8
8
  * @module typography
9
9
  */
10
+
10
11
  export type { TypeScaleTokensType } from "./type-scale-tokens";
11
12
  export { TypeScaleTokens } from "./type-scale-tokens";
13
+
12
14
  export type { TypographyProviderProps } from "./typography";
13
- export { Typography, TypographyContext, TypographyProvider, useTypography, } from "./typography";
15
+ export {
16
+ Typography,
17
+ TypographyContext,
18
+ TypographyProvider,
19
+ useTypography,
20
+ } from "./typography";
21
+
14
22
  export { TypographyKeyTokens } from "./typography-key-tokens";
15
- export type { FontVariationAxes, TextStyle, TypographyTokensOptions, } from "./typography-tokens";
16
- export { DEFAULT_FONT_VARIATION_AXES, MD3_EXPRESSIVE_FONT_VARIATION, serializeFontVariationAxes, TypographyTokens, } from "./typography-tokens";
23
+
24
+ export type {
25
+ FontVariationAxes,
26
+ TextStyle,
27
+ TypographyTokensOptions,
28
+ } from "./typography-tokens";
29
+ export {
30
+ DEFAULT_FONT_VARIATION_AXES,
31
+ MD3_EXPRESSIVE_FONT_VARIATION,
32
+ serializeFontVariationAxes,
33
+ TypographyTokens,
34
+ } from "./typography-tokens";
@@ -0,0 +1,205 @@
1
+ /**
2
+ * TypeScaleTokens - MD3 Expressive Typography Physical Tokens
3
+ *
4
+ * Port of androidx.compose.material3.tokens.TypeScaleTokens
5
+ * Adapted for Web with Google Sans Flex Variable Font (ROND axis = 100).
6
+ *
7
+ * All font sizes use rem units based on 14px root (project default).
8
+ * Reference: Material Design 3 Type Scale Tokens spec.
9
+ */
10
+
11
+ const font = "'Google Sans Flex', system-ui, sans-serif";
12
+
13
+ export const TypeScaleTokens = {
14
+ // ─── DISPLAY ────────────────────────────────────────────────────────────────
15
+ DisplayLargeFont: font,
16
+ DisplayLargeWeight: 400,
17
+ DisplayLargeSize: "3.571rem", // 57sp → ~57px / 14 = 4.071, MD3 uses 57px on 16px base → 3.5625rem
18
+ DisplayLargeLineHeight: "4rem", // 64px
19
+ DisplayLargeTracking: "-0.25px",
20
+
21
+ DisplayMediumFont: font,
22
+ DisplayMediumWeight: 400,
23
+ DisplayMediumSize: "2.857rem", // 45px
24
+ DisplayMediumLineHeight: "3.286rem", // 52px
25
+ DisplayMediumTracking: "0px",
26
+
27
+ DisplaySmallFont: font,
28
+ DisplaySmallWeight: 400,
29
+ DisplaySmallSize: "2.571rem", // 36px
30
+ DisplaySmallLineHeight: "3.143rem", // 44px
31
+ DisplaySmallTracking: "0px",
32
+
33
+ // ─── HEADLINE ───────────────────────────────────────────────────────────────
34
+ HeadlineLargeFont: font,
35
+ HeadlineLargeWeight: 400,
36
+ HeadlineLargeSize: "2.286rem", // 32px
37
+ HeadlineLargeLineHeight: "2.857rem", // 40px
38
+ HeadlineLargeTracking: "0px",
39
+
40
+ HeadlineMediumFont: font,
41
+ HeadlineMediumWeight: 400,
42
+ HeadlineMediumSize: "2rem", // 28px
43
+ HeadlineMediumLineHeight: "2.571rem", // 36px
44
+ HeadlineMediumTracking: "0px",
45
+
46
+ HeadlineSmallFont: font,
47
+ HeadlineSmallWeight: 400,
48
+ HeadlineSmallSize: "1.714rem", // 24px
49
+ HeadlineSmallLineHeight: "2.286rem", // 32px
50
+ HeadlineSmallTracking: "0px",
51
+
52
+ // ─── TITLE ──────────────────────────────────────────────────────────────────
53
+ TitleLargeFont: font,
54
+ TitleLargeWeight: 400,
55
+ TitleLargeSize: "1.571rem", // 22px
56
+ TitleLargeLineHeight: "2rem", // 28px
57
+ TitleLargeTracking: "0px",
58
+
59
+ TitleMediumFont: font,
60
+ TitleMediumWeight: 500,
61
+ TitleMediumSize: "1.143rem", // 16px
62
+ TitleMediumLineHeight: "1.714rem", // 24px
63
+ TitleMediumTracking: "0.15px",
64
+
65
+ TitleSmallFont: font,
66
+ TitleSmallWeight: 500,
67
+ TitleSmallSize: "1rem", // 14px
68
+ TitleSmallLineHeight: "1.429rem", // 20px
69
+ TitleSmallTracking: "0.1px",
70
+
71
+ // ─── BODY ───────────────────────────────────────────────────────────────────
72
+ BodyLargeFont: font,
73
+ BodyLargeWeight: 400,
74
+ BodyLargeSize: "1.143rem", // 16px
75
+ BodyLargeLineHeight: "1.714rem", // 24px
76
+ BodyLargeTracking: "0.5px",
77
+
78
+ BodyMediumFont: font,
79
+ BodyMediumWeight: 400,
80
+ BodyMediumSize: "1rem", // 14px
81
+ BodyMediumLineHeight: "1.429rem", // 20px
82
+ BodyMediumTracking: "0.25px",
83
+
84
+ BodySmallFont: font,
85
+ BodySmallWeight: 400,
86
+ BodySmallSize: "0.857rem", // 12px
87
+ BodySmallLineHeight: "1.143rem", // 16px
88
+ BodySmallTracking: "0.4px",
89
+
90
+ // ─── LABEL ──────────────────────────────────────────────────────────────────
91
+ LabelLargeFont: font,
92
+ LabelLargeWeight: 500,
93
+ LabelLargeSize: "1rem", // 14px
94
+ LabelLargeLineHeight: "1.429rem", // 20px
95
+ LabelLargeTracking: "0.1px",
96
+
97
+ LabelMediumFont: font,
98
+ LabelMediumWeight: 500,
99
+ LabelMediumSize: "0.857rem", // 12px
100
+ LabelMediumLineHeight: "1.143rem", // 16px
101
+ LabelMediumTracking: "0.5px",
102
+
103
+ LabelSmallFont: font,
104
+ LabelSmallWeight: 500,
105
+ LabelSmallSize: "0.786rem", // 11px
106
+ LabelSmallLineHeight: "1.143rem", // 16px
107
+ LabelSmallTracking: "0.5px",
108
+
109
+ // ─── EMPHASIZED (MD3 Expressive) ────────────────────────────────────────────
110
+ // Emphasized styles share Font/Size/LineHeight/Tracking with baseline.
111
+ // Only fontWeight is elevated to create visual emphasis.
112
+ // Source: TypographyTokens.kt (Emphasized entries reference same scale)
113
+
114
+ DisplayLargeEmphasizedFont: font,
115
+ DisplayLargeEmphasizedWeight: 800,
116
+ DisplayLargeEmphasizedSize: "3.571rem",
117
+ DisplayLargeEmphasizedLineHeight: "4rem",
118
+ DisplayLargeEmphasizedTracking: "-0.25px",
119
+
120
+ DisplayMediumEmphasizedFont: font,
121
+ DisplayMediumEmphasizedWeight: 800,
122
+ DisplayMediumEmphasizedSize: "2.857rem",
123
+ DisplayMediumEmphasizedLineHeight: "3.286rem",
124
+ DisplayMediumEmphasizedTracking: "0px",
125
+
126
+ DisplaySmallEmphasizedFont: font,
127
+ DisplaySmallEmphasizedWeight: 800,
128
+ DisplaySmallEmphasizedSize: "2.571rem",
129
+ DisplaySmallEmphasizedLineHeight: "3.143rem",
130
+ DisplaySmallEmphasizedTracking: "0px",
131
+
132
+ HeadlineLargeEmphasizedFont: font,
133
+ HeadlineLargeEmphasizedWeight: 800,
134
+ HeadlineLargeEmphasizedSize: "2.286rem",
135
+ HeadlineLargeEmphasizedLineHeight: "2.857rem",
136
+ HeadlineLargeEmphasizedTracking: "0px",
137
+
138
+ HeadlineMediumEmphasizedFont: font,
139
+ HeadlineMediumEmphasizedWeight: 800,
140
+ HeadlineMediumEmphasizedSize: "2rem",
141
+ HeadlineMediumEmphasizedLineHeight: "2.571rem",
142
+ HeadlineMediumEmphasizedTracking: "0px",
143
+
144
+ HeadlineSmallEmphasizedFont: font,
145
+ HeadlineSmallEmphasizedWeight: 800,
146
+ HeadlineSmallEmphasizedSize: "1.714rem",
147
+ HeadlineSmallEmphasizedLineHeight: "2.286rem",
148
+ HeadlineSmallEmphasizedTracking: "0px",
149
+
150
+ TitleLargeEmphasizedFont: font,
151
+ TitleLargeEmphasizedWeight: 700,
152
+ TitleLargeEmphasizedSize: "1.571rem",
153
+ TitleLargeEmphasizedLineHeight: "2rem",
154
+ TitleLargeEmphasizedTracking: "0px",
155
+
156
+ TitleMediumEmphasizedFont: font,
157
+ TitleMediumEmphasizedWeight: 700,
158
+ TitleMediumEmphasizedSize: "1.143rem",
159
+ TitleMediumEmphasizedLineHeight: "1.714rem",
160
+ TitleMediumEmphasizedTracking: "0.15px",
161
+
162
+ TitleSmallEmphasizedFont: font,
163
+ TitleSmallEmphasizedWeight: 700,
164
+ TitleSmallEmphasizedSize: "1rem",
165
+ TitleSmallEmphasizedLineHeight: "1.429rem",
166
+ TitleSmallEmphasizedTracking: "0.1px",
167
+
168
+ BodyLargeEmphasizedFont: font,
169
+ BodyLargeEmphasizedWeight: 700,
170
+ BodyLargeEmphasizedSize: "1.143rem",
171
+ BodyLargeEmphasizedLineHeight: "1.714rem",
172
+ BodyLargeEmphasizedTracking: "0.5px",
173
+
174
+ BodyMediumEmphasizedFont: font,
175
+ BodyMediumEmphasizedWeight: 700,
176
+ BodyMediumEmphasizedSize: "1rem",
177
+ BodyMediumEmphasizedLineHeight: "1.429rem",
178
+ BodyMediumEmphasizedTracking: "0.25px",
179
+
180
+ BodySmallEmphasizedFont: font,
181
+ BodySmallEmphasizedWeight: 700,
182
+ BodySmallEmphasizedSize: "0.857rem",
183
+ BodySmallEmphasizedLineHeight: "1.143rem",
184
+ BodySmallEmphasizedTracking: "0.4px",
185
+
186
+ LabelLargeEmphasizedFont: font,
187
+ LabelLargeEmphasizedWeight: 800,
188
+ LabelLargeEmphasizedSize: "1rem",
189
+ LabelLargeEmphasizedLineHeight: "1.429rem",
190
+ LabelLargeEmphasizedTracking: "0.1px",
191
+
192
+ LabelMediumEmphasizedFont: font,
193
+ LabelMediumEmphasizedWeight: 800,
194
+ LabelMediumEmphasizedSize: "0.857rem",
195
+ LabelMediumEmphasizedLineHeight: "1.143rem",
196
+ LabelMediumEmphasizedTracking: "0.5px",
197
+
198
+ LabelSmallEmphasizedFont: font,
199
+ LabelSmallEmphasizedWeight: 800,
200
+ LabelSmallEmphasizedSize: "0.786rem",
201
+ LabelSmallEmphasizedLineHeight: "1.143rem",
202
+ LabelSmallEmphasizedTracking: "0.5px",
203
+ } as const;
204
+
205
+ export type TypeScaleTokensType = typeof TypeScaleTokens;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * TypographyKeyTokens - MD3 Expressive Typography Key Tokens Enum
3
+ *
4
+ * Port of androidx.compose.material3.tokens.TypographyKeyTokens (Kotlin enum class).
5
+ * Used to reference a specific TextStyle via Typography.fromToken().
6
+ *
7
+ * Contains 30 keys: 15 baseline + 15 emphasized (MD3 Expressive).
8
+ */
9
+ export enum TypographyKeyTokens {
10
+ // ─── Baseline (15) ──────────────────────────────────────────────────────
11
+ BodyLarge = "BodyLarge",
12
+ BodyMedium = "BodyMedium",
13
+ BodySmall = "BodySmall",
14
+ DisplayLarge = "DisplayLarge",
15
+ DisplayMedium = "DisplayMedium",
16
+ DisplaySmall = "DisplaySmall",
17
+ HeadlineLarge = "HeadlineLarge",
18
+ HeadlineMedium = "HeadlineMedium",
19
+ HeadlineSmall = "HeadlineSmall",
20
+ LabelLarge = "LabelLarge",
21
+ LabelMedium = "LabelMedium",
22
+ LabelSmall = "LabelSmall",
23
+ TitleLarge = "TitleLarge",
24
+ TitleMedium = "TitleMedium",
25
+ TitleSmall = "TitleSmall",
26
+
27
+ // ─── Emphasized (15) - MD3 Expressive ───────────────────────────────────
28
+ BodyLargeEmphasized = "BodyLargeEmphasized",
29
+ BodyMediumEmphasized = "BodyMediumEmphasized",
30
+ BodySmallEmphasized = "BodySmallEmphasized",
31
+ DisplayLargeEmphasized = "DisplayLargeEmphasized",
32
+ DisplayMediumEmphasized = "DisplayMediumEmphasized",
33
+ DisplaySmallEmphasized = "DisplaySmallEmphasized",
34
+ HeadlineLargeEmphasized = "HeadlineLargeEmphasized",
35
+ HeadlineMediumEmphasized = "HeadlineMediumEmphasized",
36
+ HeadlineSmallEmphasized = "HeadlineSmallEmphasized",
37
+ LabelLargeEmphasized = "LabelLargeEmphasized",
38
+ LabelMediumEmphasized = "LabelMediumEmphasized",
39
+ LabelSmallEmphasized = "LabelSmallEmphasized",
40
+ TitleLargeEmphasized = "TitleLargeEmphasized",
41
+ TitleMediumEmphasized = "TitleMediumEmphasized",
42
+ TitleSmallEmphasized = "TitleSmallEmphasized",
43
+ }