@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,559 @@
1
+ /**
2
+ * @file typography.tsx
3
+ * @description MD3 Expressive Typography System for React.
4
+ *
5
+ * Port of `androidx.compose.material3.Typography` (Kotlin `@Immutable` class).
6
+ *
7
+ * Provides 30 {@link TextStyle} definitions via the React Context API, mirroring
8
+ * the Compose `LocalTypography` / `MaterialTheme.typography` pattern.
9
+ *
10
+ * ### Memory & Performance Optimizations
11
+ * - The `Typography` class delegates all property access to a `TypographyTokens`
12
+ * instance that uses **lazy getters**, so styles are computed only on first use.
13
+ * - `TypographyProvider` memoizes the context value via `useMemo` to prevent
14
+ * unnecessary re-renders downstream.
15
+ *
16
+ * @example Wrap your application
17
+ * ```tsx
18
+ * <TypographyProvider>
19
+ * <App />
20
+ * </TypographyProvider>
21
+ * ```
22
+ *
23
+ * @example Consume in a component
24
+ * ```tsx
25
+ * const typography = useTypography();
26
+ * <p style={typography.bodyLarge}>Hello</p>
27
+ * ```
28
+ *
29
+ * @example Via token key
30
+ * ```tsx
31
+ * const style = typography.fromToken(TypographyKeyTokens.BodyLarge);
32
+ * ```
33
+ *
34
+ * @example Custom font + half-rounded corners
35
+ * ```tsx
36
+ * <TypographyProvider
37
+ * fontFamily="'Roboto', sans-serif"
38
+ * fontVariationAxes={{ ROND: 50 }}
39
+ * >
40
+ * <App />
41
+ * </TypographyProvider>
42
+ * ```
43
+ */
44
+
45
+ import { createContext, type ReactNode, useContext, useMemo } from "react";
46
+ import { TypographyKeyTokens } from "./typography-key-tokens";
47
+ import {
48
+ type FontVariationAxes,
49
+ type TextStyle,
50
+ TypographyTokens,
51
+ } from "./typography-tokens";
52
+
53
+ // ─── Typography Class ─────────────────────────────────────────────────────────
54
+
55
+ /**
56
+ * MD3 Expressive Typography — port of Compose's `@Immutable class Typography(...)`.
57
+ *
58
+ * All 30 style properties are **readonly** and lazily resolved from the
59
+ * underlying {@link TypographyTokens} instance. Use {@link Typography.copy}
60
+ * to create a customized variant without mutating the original.
61
+ *
62
+ * @example Default
63
+ * ```ts
64
+ * const typography = new Typography();
65
+ * const style = typography.displayLarge; // lazy — computed on first access
66
+ * ```
67
+ *
68
+ * @example Custom tokens
69
+ * ```ts
70
+ * const typography = new Typography(
71
+ * new TypographyTokens({ fontFamily: "'Inter', sans-serif", fontVariationAxes: { ROND: 0 } })
72
+ * );
73
+ * ```
74
+ */
75
+ export class Typography {
76
+ readonly #tokens: TypographyTokens;
77
+
78
+ constructor(tokens: TypographyTokens = defaultTokens) {
79
+ this.#tokens = tokens;
80
+ }
81
+
82
+ // ─── Baseline Styles ────────────────────────────────────────────────────────
83
+
84
+ /** Display Large text style (`57px`, weight `400`). */
85
+ get displayLarge(): TextStyle {
86
+ return this.#tokens.DisplayLarge;
87
+ }
88
+ /** Display Medium text style (`45px`, weight `400`). */
89
+ get displayMedium(): TextStyle {
90
+ return this.#tokens.DisplayMedium;
91
+ }
92
+ /** Display Small text style (`36px`, weight `400`). */
93
+ get displaySmall(): TextStyle {
94
+ return this.#tokens.DisplaySmall;
95
+ }
96
+ /** Headline Large text style (`32px`, weight `400`). */
97
+ get headlineLarge(): TextStyle {
98
+ return this.#tokens.HeadlineLarge;
99
+ }
100
+ /** Headline Medium text style (`28px`, weight `400`). */
101
+ get headlineMedium(): TextStyle {
102
+ return this.#tokens.HeadlineMedium;
103
+ }
104
+ /** Headline Small text style (`24px`, weight `400`). */
105
+ get headlineSmall(): TextStyle {
106
+ return this.#tokens.HeadlineSmall;
107
+ }
108
+ /** Title Large text style (`22px`, weight `400`). */
109
+ get titleLarge(): TextStyle {
110
+ return this.#tokens.TitleLarge;
111
+ }
112
+ /** Title Medium text style (`16px`, weight `500`). */
113
+ get titleMedium(): TextStyle {
114
+ return this.#tokens.TitleMedium;
115
+ }
116
+ /** Title Small text style (`14px`, weight `500`). */
117
+ get titleSmall(): TextStyle {
118
+ return this.#tokens.TitleSmall;
119
+ }
120
+ /** Body Large text style (`16px`, weight `400`). */
121
+ get bodyLarge(): TextStyle {
122
+ return this.#tokens.BodyLarge;
123
+ }
124
+ /** Body Medium text style (`14px`, weight `400`). */
125
+ get bodyMedium(): TextStyle {
126
+ return this.#tokens.BodyMedium;
127
+ }
128
+ /** Body Small text style (`12px`, weight `400`). */
129
+ get bodySmall(): TextStyle {
130
+ return this.#tokens.BodySmall;
131
+ }
132
+ /** Label Large text style (`14px`, weight `500`). */
133
+ get labelLarge(): TextStyle {
134
+ return this.#tokens.LabelLarge;
135
+ }
136
+ /** Label Medium text style (`12px`, weight `500`). */
137
+ get labelMedium(): TextStyle {
138
+ return this.#tokens.LabelMedium;
139
+ }
140
+ /** Label Small text style (`11px`, weight `500`). */
141
+ get labelSmall(): TextStyle {
142
+ return this.#tokens.LabelSmall;
143
+ }
144
+
145
+ // ─── Emphasized Styles (MD3 Expressive) ─────────────────────────────────────
146
+
147
+ /** Display Large Emphasized text style (`57px`, weight `800`). */
148
+ get displayLargeEmphasized(): TextStyle {
149
+ return this.#tokens.DisplayLargeEmphasized;
150
+ }
151
+ /** Display Medium Emphasized text style (`45px`, weight `800`). */
152
+ get displayMediumEmphasized(): TextStyle {
153
+ return this.#tokens.DisplayMediumEmphasized;
154
+ }
155
+ /** Display Small Emphasized text style (`36px`, weight `800`). */
156
+ get displaySmallEmphasized(): TextStyle {
157
+ return this.#tokens.DisplaySmallEmphasized;
158
+ }
159
+ /** Headline Large Emphasized text style (`32px`, weight `800`). */
160
+ get headlineLargeEmphasized(): TextStyle {
161
+ return this.#tokens.HeadlineLargeEmphasized;
162
+ }
163
+ /** Headline Medium Emphasized text style (`28px`, weight `800`). */
164
+ get headlineMediumEmphasized(): TextStyle {
165
+ return this.#tokens.HeadlineMediumEmphasized;
166
+ }
167
+ /** Headline Small Emphasized text style (`24px`, weight `800`). */
168
+ get headlineSmallEmphasized(): TextStyle {
169
+ return this.#tokens.HeadlineSmallEmphasized;
170
+ }
171
+ /** Title Large Emphasized text style (`22px`, weight `700`). */
172
+ get titleLargeEmphasized(): TextStyle {
173
+ return this.#tokens.TitleLargeEmphasized;
174
+ }
175
+ /** Title Medium Emphasized text style (`16px`, weight `700`). */
176
+ get titleMediumEmphasized(): TextStyle {
177
+ return this.#tokens.TitleMediumEmphasized;
178
+ }
179
+ /** Title Small Emphasized text style (`14px`, weight `700`). */
180
+ get titleSmallEmphasized(): TextStyle {
181
+ return this.#tokens.TitleSmallEmphasized;
182
+ }
183
+ /** Body Large Emphasized text style (`16px`, weight `700`). */
184
+ get bodyLargeEmphasized(): TextStyle {
185
+ return this.#tokens.BodyLargeEmphasized;
186
+ }
187
+ /** Body Medium Emphasized text style (`14px`, weight `700`). */
188
+ get bodyMediumEmphasized(): TextStyle {
189
+ return this.#tokens.BodyMediumEmphasized;
190
+ }
191
+ /** Body Small Emphasized text style (`12px`, weight `700`). */
192
+ get bodySmallEmphasized(): TextStyle {
193
+ return this.#tokens.BodySmallEmphasized;
194
+ }
195
+ /** Label Large Emphasized text style (`14px`, weight `800`). */
196
+ get labelLargeEmphasized(): TextStyle {
197
+ return this.#tokens.LabelLargeEmphasized;
198
+ }
199
+ /** Label Medium Emphasized text style (`12px`, weight `800`). */
200
+ get labelMediumEmphasized(): TextStyle {
201
+ return this.#tokens.LabelMediumEmphasized;
202
+ }
203
+ /** Label Small Emphasized text style (`11px`, weight `800`). */
204
+ get labelSmallEmphasized(): TextStyle {
205
+ return this.#tokens.LabelSmallEmphasized;
206
+ }
207
+
208
+ // ─── Methods ─────────────────────────────────────────────────────────────────
209
+
210
+ /**
211
+ * Returns the `TextStyle` corresponding to the given {@link TypographyKeyTokens}.
212
+ *
213
+ * Port of `internal fun Typography.fromToken(value: TypographyKeyTokens): TextStyle`.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * const style = typography.fromToken(TypographyKeyTokens.BodyLarge);
218
+ * ```
219
+ */
220
+ fromToken(value: TypographyKeyTokens): TextStyle {
221
+ return this[TOKEN_TO_PROP[value]];
222
+ }
223
+
224
+ /**
225
+ * Creates a new `Typography` instance with the specified property overrides
226
+ * merged on top of the current instance's styles.
227
+ *
228
+ * Port of Compose's `fun Typography.copy(...)`.
229
+ *
230
+ * Unlike a shallow `Object.assign`, this method preserves the lazy-getter
231
+ * architecture — overridden styles are stored separately and looked up first
232
+ * on each property access, while non-overridden styles continue to be
233
+ * resolved from the underlying {@link TypographyTokens}.
234
+ *
235
+ * @param overrides - Map of camelCase property names to partial `TextStyle` overrides.
236
+ * @returns A new `Typography` instance. The original is never mutated.
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * const custom = typography.copy({ bodyLarge: { fontSize: "2rem" } });
241
+ * custom.bodyLarge.fontSize; // "2rem"
242
+ * custom.bodySmall.fontSize; // original token value — untouched
243
+ * ```
244
+ */
245
+ copy(
246
+ overrides: Partial<Record<TypographyStyleKey, Partial<TextStyle>>>,
247
+ ): Typography {
248
+ return new OverriddenTypography(this.#tokens, this, overrides);
249
+ }
250
+ }
251
+
252
+ // ─── TypographyStyleKey ───────────────────────────────────────────────────────
253
+
254
+ /**
255
+ * Union of all camelCase property names on {@link Typography} that return a
256
+ * {@link TextStyle}. Used as the key type for `copy()` overrides and the
257
+ * `OverriddenTypography` resolver.
258
+ */
259
+ type TypographyStyleKey = {
260
+ [K in keyof Typography]: Typography[K] extends TextStyle ? K : never;
261
+ }[keyof Typography];
262
+
263
+ // ─── OverriddenTypography ─────────────────────────────────────────────────────
264
+
265
+ /**
266
+ * Internal subclass used by {@link Typography.copy}.
267
+ *
268
+ * Holds a map of explicit style overrides. Each getter checks for an override
269
+ * first; if none exists, it falls through to the parent {@link Typography}.
270
+ *
271
+ * @internal
272
+ */
273
+ class OverriddenTypography extends Typography {
274
+ readonly #base: Typography;
275
+ readonly #overrides: Partial<Record<TypographyStyleKey, Partial<TextStyle>>>;
276
+
277
+ constructor(
278
+ tokens: TypographyTokens,
279
+ base: Typography,
280
+ overrides: Partial<Record<TypographyStyleKey, Partial<TextStyle>>>,
281
+ ) {
282
+ super(tokens);
283
+ this.#base = base;
284
+ this.#overrides = overrides;
285
+ }
286
+
287
+ #resolve(key: TypographyStyleKey): TextStyle {
288
+ const override = this.#overrides[key];
289
+ const base = (this.#base as unknown as Record<string, TextStyle>)[key];
290
+ return override ? { ...base, ...override } : base;
291
+ }
292
+
293
+ // ─── Baseline
294
+ override get displayLarge() {
295
+ return this.#resolve("displayLarge");
296
+ }
297
+ override get displayMedium() {
298
+ return this.#resolve("displayMedium");
299
+ }
300
+ override get displaySmall() {
301
+ return this.#resolve("displaySmall");
302
+ }
303
+ override get headlineLarge() {
304
+ return this.#resolve("headlineLarge");
305
+ }
306
+ override get headlineMedium() {
307
+ return this.#resolve("headlineMedium");
308
+ }
309
+ override get headlineSmall() {
310
+ return this.#resolve("headlineSmall");
311
+ }
312
+ override get titleLarge() {
313
+ return this.#resolve("titleLarge");
314
+ }
315
+ override get titleMedium() {
316
+ return this.#resolve("titleMedium");
317
+ }
318
+ override get titleSmall() {
319
+ return this.#resolve("titleSmall");
320
+ }
321
+ override get bodyLarge() {
322
+ return this.#resolve("bodyLarge");
323
+ }
324
+ override get bodyMedium() {
325
+ return this.#resolve("bodyMedium");
326
+ }
327
+ override get bodySmall() {
328
+ return this.#resolve("bodySmall");
329
+ }
330
+ override get labelLarge() {
331
+ return this.#resolve("labelLarge");
332
+ }
333
+ override get labelMedium() {
334
+ return this.#resolve("labelMedium");
335
+ }
336
+ override get labelSmall() {
337
+ return this.#resolve("labelSmall");
338
+ }
339
+ // ─── Emphasized
340
+ override get displayLargeEmphasized() {
341
+ return this.#resolve("displayLargeEmphasized");
342
+ }
343
+ override get displayMediumEmphasized() {
344
+ return this.#resolve("displayMediumEmphasized");
345
+ }
346
+ override get displaySmallEmphasized() {
347
+ return this.#resolve("displaySmallEmphasized");
348
+ }
349
+ override get headlineLargeEmphasized() {
350
+ return this.#resolve("headlineLargeEmphasized");
351
+ }
352
+ override get headlineMediumEmphasized() {
353
+ return this.#resolve("headlineMediumEmphasized");
354
+ }
355
+ override get headlineSmallEmphasized() {
356
+ return this.#resolve("headlineSmallEmphasized");
357
+ }
358
+ override get titleLargeEmphasized() {
359
+ return this.#resolve("titleLargeEmphasized");
360
+ }
361
+ override get titleMediumEmphasized() {
362
+ return this.#resolve("titleMediumEmphasized");
363
+ }
364
+ override get titleSmallEmphasized() {
365
+ return this.#resolve("titleSmallEmphasized");
366
+ }
367
+ override get bodyLargeEmphasized() {
368
+ return this.#resolve("bodyLargeEmphasized");
369
+ }
370
+ override get bodyMediumEmphasized() {
371
+ return this.#resolve("bodyMediumEmphasized");
372
+ }
373
+ override get bodySmallEmphasized() {
374
+ return this.#resolve("bodySmallEmphasized");
375
+ }
376
+ override get labelLargeEmphasized() {
377
+ return this.#resolve("labelLargeEmphasized");
378
+ }
379
+ override get labelMediumEmphasized() {
380
+ return this.#resolve("labelMediumEmphasized");
381
+ }
382
+ override get labelSmallEmphasized() {
383
+ return this.#resolve("labelSmallEmphasized");
384
+ }
385
+ }
386
+
387
+ // ─── Token → Property lookup (avoids recreating the map on every fromToken call) ──
388
+
389
+ type TypographyProp = {
390
+ [K in keyof Typography]: Typography[K] extends TextStyle ? K : never;
391
+ }[keyof Typography];
392
+
393
+ const TOKEN_TO_PROP: Record<TypographyKeyTokens, TypographyProp> = {
394
+ [TypographyKeyTokens.DisplayLarge]: "displayLarge",
395
+ [TypographyKeyTokens.DisplayMedium]: "displayMedium",
396
+ [TypographyKeyTokens.DisplaySmall]: "displaySmall",
397
+ [TypographyKeyTokens.HeadlineLarge]: "headlineLarge",
398
+ [TypographyKeyTokens.HeadlineMedium]: "headlineMedium",
399
+ [TypographyKeyTokens.HeadlineSmall]: "headlineSmall",
400
+ [TypographyKeyTokens.TitleLarge]: "titleLarge",
401
+ [TypographyKeyTokens.TitleMedium]: "titleMedium",
402
+ [TypographyKeyTokens.TitleSmall]: "titleSmall",
403
+ [TypographyKeyTokens.BodyLarge]: "bodyLarge",
404
+ [TypographyKeyTokens.BodyMedium]: "bodyMedium",
405
+ [TypographyKeyTokens.BodySmall]: "bodySmall",
406
+ [TypographyKeyTokens.LabelLarge]: "labelLarge",
407
+ [TypographyKeyTokens.LabelMedium]: "labelMedium",
408
+ [TypographyKeyTokens.LabelSmall]: "labelSmall",
409
+ [TypographyKeyTokens.DisplayLargeEmphasized]: "displayLargeEmphasized",
410
+ [TypographyKeyTokens.DisplayMediumEmphasized]: "displayMediumEmphasized",
411
+ [TypographyKeyTokens.DisplaySmallEmphasized]: "displaySmallEmphasized",
412
+ [TypographyKeyTokens.HeadlineLargeEmphasized]: "headlineLargeEmphasized",
413
+ [TypographyKeyTokens.HeadlineMediumEmphasized]: "headlineMediumEmphasized",
414
+ [TypographyKeyTokens.HeadlineSmallEmphasized]: "headlineSmallEmphasized",
415
+ [TypographyKeyTokens.TitleLargeEmphasized]: "titleLargeEmphasized",
416
+ [TypographyKeyTokens.TitleMediumEmphasized]: "titleMediumEmphasized",
417
+ [TypographyKeyTokens.TitleSmallEmphasized]: "titleSmallEmphasized",
418
+ [TypographyKeyTokens.BodyLargeEmphasized]: "bodyLargeEmphasized",
419
+ [TypographyKeyTokens.BodyMediumEmphasized]: "bodyMediumEmphasized",
420
+ [TypographyKeyTokens.BodySmallEmphasized]: "bodySmallEmphasized",
421
+ [TypographyKeyTokens.LabelLargeEmphasized]: "labelLargeEmphasized",
422
+ [TypographyKeyTokens.LabelMediumEmphasized]: "labelMediumEmphasized",
423
+ [TypographyKeyTokens.LabelSmallEmphasized]: "labelSmallEmphasized",
424
+ };
425
+
426
+ // ─── React Context API ────────────────────────────────────────────────────────
427
+
428
+ /** Singleton default token instance (shared; never mutated). */
429
+ const defaultTokens = new TypographyTokens();
430
+
431
+ /**
432
+ * Default {@link Typography} instance used as the context fallback when no
433
+ * `TypographyProvider` is present in the tree.
434
+ *
435
+ * Mirrors `private val LocalTypography = staticCompositionLocalOf { Typography() }`.
436
+ */
437
+ const defaultTypography = new Typography();
438
+
439
+ /**
440
+ * React context that holds the current {@link Typography} instance.
441
+ *
442
+ * Port of `internal val LocalTypography = staticCompositionLocalOf { Typography() }`.
443
+ *
444
+ * @internal — Prefer {@link useTypography} and {@link TypographyProvider}.
445
+ */
446
+ export const TypographyContext = createContext<Typography>(defaultTypography);
447
+
448
+ /**
449
+ * React hook to access the current {@link Typography} from the nearest
450
+ * {@link TypographyProvider} in the tree. Falls back to the default
451
+ * googleapis Typography when no provider is present.
452
+ *
453
+ * @returns The current `Typography` instance.
454
+ *
455
+ * @example
456
+ * ```tsx
457
+ * const typography = useTypography();
458
+ * <p style={typography.bodyLarge}>Hello</p>
459
+ * ```
460
+ */
461
+ export function useTypography(): Typography {
462
+ return useContext(TypographyContext);
463
+ }
464
+
465
+ // ─── TypographyProvider ───────────────────────────────────────────────────────
466
+
467
+ /**
468
+ * Props for {@link TypographyProvider}.
469
+ */
470
+ export interface TypographyProviderProps {
471
+ /** The child tree that will have access to the provided typography. */
472
+ children: ReactNode;
473
+ /**
474
+ * A fully custom {@link Typography} instance.
475
+ * When provided, `fontFamily` and `fontVariationAxes` are ignored.
476
+ */
477
+ typography?: Typography;
478
+ /**
479
+ * Shorthand to override the CSS `font-family` for all typography styles.
480
+ * Ignored when `typography` is provided.
481
+ *
482
+ * @example "'Roboto', sans-serif"
483
+ */
484
+ fontFamily?: string;
485
+ /**
486
+ * Variable font axes to apply globally via `font-variation-settings`.
487
+ * Merged on top of the defaults (`ROND: 100`). Only the axes you specify
488
+ * will be overridden; unspecified axes retain font defaults.
489
+ * Ignored when `typography` is provided.
490
+ *
491
+ * @example { ROND: 50 } // half-rounded
492
+ * @example { ROND: 0 } // sharp corners
493
+ */
494
+ fontVariationAxes?: FontVariationAxes;
495
+ }
496
+
497
+ /**
498
+ * Typography Provider component.
499
+ *
500
+ * Port of `CompositionLocalProvider(LocalTypography provides typography)`.
501
+ * Wrap your application (or a subtree) to provide MD3 Expressive typography
502
+ * to all descendant components that call {@link useTypography}.
503
+ *
504
+ * The context value is **memoized** — it is only re-computed when `typography`,
505
+ * `fontFamily`, or `fontVariationAxes` change, preventing unnecessary re-renders.
506
+ *
507
+ * @example Default (Google Sans Flex, ROND = 100)
508
+ * ```tsx
509
+ * <TypographyProvider>
510
+ * <App />
511
+ * </TypographyProvider>
512
+ * ```
513
+ *
514
+ * @example Custom font
515
+ * ```tsx
516
+ * <TypographyProvider fontFamily="'Inter', sans-serif">
517
+ * <App />
518
+ * </TypographyProvider>
519
+ * ```
520
+ *
521
+ * @example Partially rounded corners (ROND = 50)
522
+ * ```tsx
523
+ * <TypographyProvider fontVariationAxes={{ ROND: 50 }}>
524
+ * <App />
525
+ * </TypographyProvider>
526
+ * ```
527
+ *
528
+ * @example Fully sharp (ROND = 0) with a custom font
529
+ * ```tsx
530
+ * <TypographyProvider
531
+ * fontFamily="'Outfit', sans-serif"
532
+ * fontVariationAxes={{ ROND: 0 }}
533
+ * >
534
+ * <App />
535
+ * </TypographyProvider>
536
+ * ```
537
+ */
538
+ export function TypographyProvider({
539
+ children,
540
+ typography,
541
+ fontFamily,
542
+ fontVariationAxes,
543
+ }: TypographyProviderProps) {
544
+ const value = useMemo<Typography>(() => {
545
+ if (typography) return typography;
546
+ if (fontFamily || fontVariationAxes) {
547
+ return new Typography(
548
+ new TypographyTokens({ fontFamily, fontVariationAxes }),
549
+ );
550
+ }
551
+ return defaultTypography;
552
+ }, [typography, fontFamily, fontVariationAxes]);
553
+
554
+ return (
555
+ <TypographyContext.Provider value={value}>
556
+ {children}
557
+ </TypographyContext.Provider>
558
+ );
559
+ }
@@ -0,0 +1,4 @@
1
+ import { renderToString } from "react-dom/server";
2
+ import VerticalMenuGapDemo from "../../apps/docs/registry/demos/vertical-menu-gap-demo";
3
+
4
+ console.log(renderToString(<VerticalMenuGapDemo />));
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <style>
5
+ .outer {
6
+ width: 200px;
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: 2px;
10
+ box-shadow: 0px 4px 8px rgba(0,0,0,0.5);
11
+ border-radius: 16px;
12
+ overflow: hidden;
13
+ }
14
+ .inner {
15
+ background: white;
16
+ height: 100px;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body style="background: #ccc; padding: 50px;">
21
+ <div class="outer">
22
+ <div class="inner" style="border-radius: 16px 16px 8px 8px;"></div>
23
+ <div class="inner" style="border-radius: 4px 4px 12px 12px;"></div>
24
+ </div>
25
+ </body>
26
+ </html>