@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,75 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import { LargeFlexibleAppBar } from "./large-flexible-app-bar";
4
+ import { MediumFlexibleAppBar } from "./medium-flexible-app-bar";
5
+
6
+ describe("FlexibleAppBars", () => {
7
+ describe("MediumFlexibleAppBar", () => {
8
+ it("renders correctly with essential props", () => {
9
+ render(
10
+ <MediumFlexibleAppBar
11
+ title="Medium Title"
12
+ navigationIcon={
13
+ <button type="button" data-testid="medium-nav">
14
+ Menu
15
+ </button>
16
+ }
17
+ actions={
18
+ <button type="button" data-testid="medium-actions">
19
+ Edit
20
+ </button>
21
+ }
22
+ />,
23
+ );
24
+
25
+ // Should render title inside the expanded area and pinned area (sometimes twice, or just once if transitioning)
26
+ expect(screen.getAllByText("Medium Title").length).toBeGreaterThan(0);
27
+ expect(screen.getByTestId("medium-nav")).toBeInTheDocument();
28
+ expect(screen.getByTestId("medium-actions")).toBeInTheDocument();
29
+ });
30
+
31
+ it("renders subtitle if provided", () => {
32
+ render(
33
+ <MediumFlexibleAppBar
34
+ title="Has Subtitle"
35
+ subtitle="Medium Subtitle"
36
+ />,
37
+ );
38
+ expect(screen.getAllByText("Medium Subtitle").length).toBeGreaterThan(0);
39
+ });
40
+ });
41
+
42
+ describe("LargeFlexibleAppBar", () => {
43
+ it("renders correctly with essential props", () => {
44
+ render(
45
+ <LargeFlexibleAppBar
46
+ title="Large Title"
47
+ navigationIcon={
48
+ <button type="button" data-testid="large-nav">
49
+ Close
50
+ </button>
51
+ }
52
+ actions={
53
+ <button type="button" data-testid="large-actions">
54
+ Save
55
+ </button>
56
+ }
57
+ />,
58
+ );
59
+
60
+ expect(screen.getAllByText("Large Title").length).toBeGreaterThan(0);
61
+ expect(screen.getByTestId("large-nav")).toBeInTheDocument();
62
+ expect(screen.getByTestId("large-actions")).toBeInTheDocument();
63
+ });
64
+
65
+ it("renders headerContent", () => {
66
+ render(
67
+ <LargeFlexibleAppBar
68
+ title="With Header Content"
69
+ headerContent={<div data-testid="header-content">Custom Content</div>}
70
+ />,
71
+ );
72
+ expect(screen.getByTestId("header-content")).toBeInTheDocument();
73
+ });
74
+ });
75
+ });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @file use-app-bar-scroll.ts
3
+ * MD3 Expressive App Bar — Scroll behavior hook.
4
+ *
5
+ * Tracks scroll state for App Bar behaviors:
6
+ * - `pinned`: background color change only
7
+ * - `enterAlways`: hide/show based on scroll direction
8
+ * - `exitUntilCollapsed`: drives collapse fraction (0 = expanded, 1 = collapsed)
9
+ */
10
+
11
+ import * as React from "react";
12
+ import type {
13
+ AppBarScrollBehavior,
14
+ UseAppBarScrollReturn,
15
+ } from "../app-bar.types";
16
+
17
+ interface UseAppBarScrollOptions {
18
+ /** Ref to the scrollable container. Defaults to `window`. */
19
+ scrollElement?: React.RefObject<HTMLElement | null>;
20
+ /** Scroll behavior mode. @default "pinned" */
21
+ behavior?: AppBarScrollBehavior;
22
+ /** Collapsed height in px — used for `exitUntilCollapsed`. @default 64 */
23
+ collapsedHeight?: number;
24
+ /** Expanded height in px — used for `exitUntilCollapsed`. @default 112 */
25
+ expandedHeight?: number;
26
+ }
27
+
28
+ /**
29
+ * Tracks scroll position and derives App Bar state for all three behaviors.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * // pinned (color change only)
34
+ * const { isScrolled } = useAppBarScroll({ behavior: 'pinned' });
35
+ *
36
+ * // enterAlways (hide/show)
37
+ * const { isHidden } = useAppBarScroll({ behavior: 'enterAlways' });
38
+ *
39
+ * // exitUntilCollapsed (collapse animation)
40
+ * const { collapsedFraction } = useAppBarScroll({
41
+ * behavior: 'exitUntilCollapsed',
42
+ * collapsedHeight: 64,
43
+ * expandedHeight: 112,
44
+ * });
45
+ * ```
46
+ */
47
+ export function useAppBarScroll({
48
+ scrollElement,
49
+ behavior = "pinned",
50
+ collapsedHeight = 64,
51
+ expandedHeight = 112,
52
+ }: UseAppBarScrollOptions = {}): UseAppBarScrollReturn {
53
+ const [isScrolled, setIsScrolled] = React.useState(false);
54
+ const [collapsedFraction, setCollapsedFraction] = React.useState(0);
55
+ const [isHidden, setIsHidden] = React.useState(false);
56
+
57
+ // Track previous scroll position for direction detection
58
+ const prevScrollYRef = React.useRef(0);
59
+ // Hysteresis to prevent rapid toggling on enterAlways
60
+ const hideThreshold = 64;
61
+
62
+ React.useEffect(() => {
63
+ const scrollDistance = expandedHeight - collapsedHeight;
64
+
65
+ const getScrollY = (): number => {
66
+ const el = scrollElement?.current;
67
+ return el ? el.scrollTop : window.scrollY;
68
+ };
69
+
70
+ const handleScroll = () => {
71
+ const currentY = getScrollY();
72
+ const prevY = prevScrollYRef.current;
73
+ const delta = currentY - prevY;
74
+ prevScrollYRef.current = currentY;
75
+
76
+ // Pinned: only toggle background color
77
+ setIsScrolled(currentY > 0);
78
+
79
+ if (behavior === "exitUntilCollapsed") {
80
+ const fraction =
81
+ scrollDistance > 0
82
+ ? Math.min(1, Math.max(0, currentY / scrollDistance))
83
+ : 0;
84
+ setCollapsedFraction(fraction);
85
+ }
86
+
87
+ if (behavior === "enterAlways") {
88
+ if (delta > 0 && currentY > hideThreshold) {
89
+ // Scrolling down — hide
90
+ setIsHidden(true);
91
+ } else if (delta < 0) {
92
+ // Scrolling up — show
93
+ setIsHidden(false);
94
+ }
95
+ }
96
+ };
97
+
98
+ const target = scrollElement?.current ?? window;
99
+ target.addEventListener("scroll", handleScroll, { passive: true });
100
+
101
+ // Sync initial state
102
+ handleScroll();
103
+
104
+ return () => {
105
+ target.removeEventListener("scroll", handleScroll);
106
+ };
107
+ }, [scrollElement, behavior, collapsedHeight, expandedHeight]);
108
+
109
+ return { isScrolled, collapsedFraction, isHidden };
110
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @file use-flexible-app-bar.ts
3
+ * Shared animation hook for MediumFlexibleAppBar and LargeFlexibleAppBar.
4
+ *
5
+ * Extracts common scroll-driven animation state to avoid duplication.
6
+ */
7
+
8
+ import type { MotionValue } from "motion/react";
9
+ import { useMotionValue, useReducedMotion, useTransform } from "motion/react";
10
+ import type { RefObject } from "react";
11
+ import * as React from "react";
12
+ import { APP_BAR_COLOR_TRANSITION, APP_BAR_COLORS } from "../app-bar.tokens";
13
+ import type { AppBarColors } from "../app-bar.types";
14
+ import { useAppBarScroll } from "./use-app-bar-scroll";
15
+
16
+ interface UseFlexibleAppBarOptions {
17
+ collapsedHeight: number;
18
+ expandedHeight: number;
19
+ scrollElement?: RefObject<HTMLElement | null>;
20
+ colors?: AppBarColors;
21
+ /** [start, end] progress range where large title fades from 1 → 0 */
22
+ largeTitleFadeRange: [number, number];
23
+ /** [start, end] progress range where small title fades from 0 → 1 */
24
+ smallTitleFadeRange: [number, number];
25
+ /** [start, end] progress range where subtitle fades from 1 → 0 */
26
+ subtitleFadeRange: [number, number];
27
+ /** [start, end] progress range where headerContent fades from 1 → 0 */
28
+ headerContentFadeRange: [number, number];
29
+ /** [expanded, collapsed] Y offset for large title */
30
+ largeTitleYRange: [number, number];
31
+ }
32
+
33
+ export interface FlexibleAppBarAnimationState {
34
+ height: MotionValue<number>;
35
+ currentBg: string;
36
+ cssTransition: string | undefined;
37
+ largeTitleOpacity: MotionValue<number>;
38
+ largeTitleY: MotionValue<number>;
39
+ smallTitleOpacity: MotionValue<number>;
40
+ subtitleOpacity: MotionValue<number>;
41
+ headerContentOpacity: MotionValue<number>;
42
+ }
43
+
44
+ export function useFlexibleAppBar({
45
+ collapsedHeight,
46
+ expandedHeight,
47
+ scrollElement,
48
+ colors,
49
+ largeTitleFadeRange,
50
+ smallTitleFadeRange,
51
+ subtitleFadeRange,
52
+ headerContentFadeRange,
53
+ largeTitleYRange,
54
+ }: UseFlexibleAppBarOptions): FlexibleAppBarAnimationState {
55
+ const shouldReduceMotion = useReducedMotion();
56
+
57
+ const { collapsedFraction } = useAppBarScroll({
58
+ scrollElement,
59
+ behavior: "exitUntilCollapsed",
60
+ collapsedHeight,
61
+ expandedHeight,
62
+ });
63
+
64
+ const scrollProgress = useMotionValue(0);
65
+
66
+ React.useEffect(() => {
67
+ scrollProgress.set(
68
+ shouldReduceMotion
69
+ ? collapsedFraction > 0.5
70
+ ? 1
71
+ : 0
72
+ : collapsedFraction,
73
+ );
74
+ }, [collapsedFraction, scrollProgress, shouldReduceMotion]);
75
+
76
+ const height = useTransform(
77
+ scrollProgress,
78
+ [0, 1],
79
+ [expandedHeight, collapsedHeight],
80
+ );
81
+
82
+ const containerBg = colors?.containerColor ?? APP_BAR_COLORS.container;
83
+ const scrolledBg =
84
+ colors?.scrolledContainerColor ?? APP_BAR_COLORS.scrolledContainer;
85
+ const currentBg = collapsedFraction > 0 ? scrolledBg : containerBg;
86
+
87
+ const cssTransition = shouldReduceMotion
88
+ ? undefined
89
+ : `background-color ${APP_BAR_COLOR_TRANSITION.duration}s cubic-bezier(${APP_BAR_COLOR_TRANSITION.ease.join(",")})`;
90
+
91
+ const largeTitleOpacity = useTransform(
92
+ scrollProgress,
93
+ [largeTitleFadeRange[0], largeTitleFadeRange[1]],
94
+ [1, 0],
95
+ );
96
+ const largeTitleY = useTransform(scrollProgress, [0, 1], largeTitleYRange);
97
+ const smallTitleOpacity = useTransform(
98
+ scrollProgress,
99
+ [smallTitleFadeRange[0], smallTitleFadeRange[1]],
100
+ [0, 1],
101
+ );
102
+ const subtitleOpacity = useTransform(
103
+ scrollProgress,
104
+ [subtitleFadeRange[0], subtitleFadeRange[1]],
105
+ [1, 0],
106
+ );
107
+ const headerContentOpacity = useTransform(
108
+ scrollProgress,
109
+ [headerContentFadeRange[0], headerContentFadeRange[1]],
110
+ [1, 0],
111
+ );
112
+
113
+ return {
114
+ height,
115
+ currentBg,
116
+ cssTransition,
117
+ largeTitleOpacity,
118
+ largeTitleY,
119
+ smallTitleOpacity,
120
+ subtitleOpacity,
121
+ headerContentOpacity,
122
+ };
123
+ }
@@ -24,16 +24,49 @@
24
24
  * - APP_BAR_COLORS: CSS custom property color references
25
25
  * - Animation constants (APP_BAR_COLOR_TRANSITION, etc.)
26
26
  */
27
- export { APP_BAR_BOTTOM_SPRING, APP_BAR_COLOR_TRANSITION, APP_BAR_COLORS, APP_BAR_ENTER_ALWAYS_SPRING, APP_BAR_TITLE_FADE, AppBarTokens, appBarTypography, SEARCH_VIEW_SPRING, } from "./app-bar.tokens";
28
- export type { AppBarColors, AppBarColumnProps, AppBarItem, AppBarItemType, AppBarMenuState, AppBarOverflowIndicatorProps, AppBarRowProps, AppBarScrollBehavior, BaseAppBarProps, BottomAppBarProps, DockedToolbarProps, FlexibleAppBarProps, SearchAppBarProps, SearchBarVariant, SearchViewProps, SmallAppBarProps, TitleAlignment, UseAppBarScrollReturn, } from "./app-bar.types";
27
+
28
+ // ─── Tokens ───────────────────────────────────────────────────────────────────
29
+ export {
30
+ APP_BAR_BOTTOM_SPRING,
31
+ APP_BAR_COLOR_TRANSITION,
32
+ APP_BAR_COLORS,
33
+ APP_BAR_ENTER_ALWAYS_SPRING,
34
+ APP_BAR_TITLE_FADE,
35
+ AppBarTokens,
36
+ appBarTypography,
37
+ SEARCH_VIEW_SPRING,
38
+ } from "./app-bar.tokens";
39
+ // ─── Types ────────────────────────────────────────────────────────────────────
40
+ export type {
41
+ AppBarColors,
42
+ AppBarColumnProps,
43
+ AppBarItem,
44
+ AppBarItemType,
45
+ AppBarMenuState,
46
+ AppBarOverflowIndicatorProps,
47
+ AppBarRowProps,
48
+ AppBarScrollBehavior,
49
+ BaseAppBarProps,
50
+ BottomAppBarProps,
51
+ DockedToolbarProps,
52
+ FlexibleAppBarProps,
53
+ SearchAppBarProps,
54
+ SearchBarVariant,
55
+ SearchViewProps,
56
+ SmallAppBarProps,
57
+ TitleAlignment,
58
+ UseAppBarScrollReturn,
59
+ } from "./app-bar.types";
29
60
  export { AppBarColumn } from "./app-bar-column";
30
61
  export { AppBarOverflowIndicator } from "./app-bar-overflow-indicator";
31
62
  export { AppBarRow } from "./app-bar-row";
32
63
  export { BottomAppBar } from "./bottom-app-bar";
33
64
  export { DockedToolbar } from "./docked-toolbar";
65
+ // ─── Hook ─────────────────────────────────────────────────────────────────────
34
66
  export { useAppBarScroll } from "./hooks/use-app-bar-scroll";
35
67
  export { LargeFlexibleAppBar } from "./large-flexible-app-bar";
36
68
  export { MediumFlexibleAppBar } from "./medium-flexible-app-bar";
37
69
  export { SearchAppBar } from "./search-app-bar";
38
70
  export { SearchView, SearchViewContainer } from "./search-view";
71
+ // ─── Components ───────────────────────────────────────────────────────────────
39
72
  export { SmallAppBar } from "./small-app-bar";
@@ -0,0 +1,165 @@
1
+ /**
2
+ * @file large-flexible-app-bar.tsx
3
+ * MD3 Expressive Large Flexible App Bar.
4
+ *
5
+ * Like MediumFlexibleAppBar but with larger typography.
6
+ * Expanded height: 120px (no subtitle) / 152px (with subtitle)
7
+ * Collapsed height: 64px
8
+ * Title crossfade: DisplaySmall (expanded) ↔ TitleLarge (collapsed)
9
+ *
10
+ * @see docs/m3/app-bars/AppBarLargeFlexibleTokens.kt
11
+ */
12
+
13
+ import { m } from "motion/react";
14
+ import { cn } from "../../lib/utils";
15
+ import {
16
+ APP_BAR_COLORS,
17
+ AppBarTokens,
18
+ appBarTypography,
19
+ } from "./app-bar.tokens";
20
+ import type { FlexibleAppBarProps } from "./app-bar.types";
21
+ import { useFlexibleAppBar } from "./hooks/use-flexible-app-bar";
22
+
23
+ /**
24
+ * MD3 Expressive Large Flexible App Bar.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * <LargeFlexibleAppBar
29
+ * title="Discover"
30
+ * subtitle="Trending today"
31
+ * navigationIcon={<IconButton aria-label="Open menu"><Icon>menu</Icon></IconButton>}
32
+ * headerContent={<img src="/banner.jpg" alt="" className="rounded-xl h-20 w-full object-cover" />}
33
+ * />
34
+ * ```
35
+ */
36
+ export function LargeFlexibleAppBar({
37
+ title,
38
+ subtitle,
39
+ titleAlignment = "start",
40
+ navigationIcon,
41
+ actions,
42
+ colors,
43
+ scrollElement,
44
+ headerContent,
45
+ collapsedHeight = AppBarTokens.heights.flexibleCollapsed,
46
+ expandedHeight,
47
+ className,
48
+ }: FlexibleAppBarProps) {
49
+ const resolvedExpandedHeight =
50
+ expandedHeight ??
51
+ (subtitle
52
+ ? AppBarTokens.heights.largeFlexWithSubtitleExpanded
53
+ : AppBarTokens.heights.largeFlexExpanded);
54
+
55
+ const {
56
+ height,
57
+ currentBg,
58
+ cssTransition,
59
+ largeTitleOpacity,
60
+ largeTitleY,
61
+ smallTitleOpacity,
62
+ subtitleOpacity,
63
+ headerContentOpacity,
64
+ } = useFlexibleAppBar({
65
+ collapsedHeight,
66
+ expandedHeight: resolvedExpandedHeight,
67
+ scrollElement,
68
+ colors,
69
+ largeTitleFadeRange: [0, 0.35],
70
+ smallTitleFadeRange: [0.55, 0.95],
71
+ subtitleFadeRange: [0, 0.25],
72
+ headerContentFadeRange: [0, 0.2],
73
+ largeTitleYRange: [0, 12],
74
+ });
75
+
76
+ const isCentered = titleAlignment === "center";
77
+
78
+ return (
79
+ <m.header
80
+ role="banner"
81
+ className={cn(
82
+ "fixed top-0 inset-x-0 z-50 flex flex-col overflow-hidden",
83
+ className,
84
+ )}
85
+ style={{ height, backgroundColor: currentBg, transition: cssTransition }}
86
+ >
87
+ <div
88
+ className="flex items-center px-1 shrink-0"
89
+ style={{ height: collapsedHeight }}
90
+ >
91
+ {navigationIcon && (
92
+ <div
93
+ className="shrink-0 flex items-center justify-center"
94
+ style={{
95
+ width: AppBarTokens.iconButtonTouchTarget,
96
+ height: AppBarTokens.iconButtonTouchTarget,
97
+ }}
98
+ >
99
+ {navigationIcon}
100
+ </div>
101
+ )}
102
+
103
+ <m.div
104
+ className={cn("flex-1 min-w-0", !navigationIcon && "pl-4")}
105
+ style={{ opacity: smallTitleOpacity }}
106
+ aria-hidden="true"
107
+ >
108
+ <span
109
+ className={cn(
110
+ appBarTypography.titleLarge,
111
+ "truncate block",
112
+ isCentered && "text-center",
113
+ )}
114
+ style={{ color: colors?.titleColor ?? APP_BAR_COLORS.title }}
115
+ >
116
+ {title}
117
+ </span>
118
+ </m.div>
119
+
120
+ {actions && <div className="flex items-center shrink-0">{actions}</div>}
121
+ </div>
122
+
123
+ <div
124
+ className={cn(
125
+ "flex flex-col flex-1 px-4 pb-4 justify-end",
126
+ isCentered ? "items-center" : "items-start",
127
+ )}
128
+ >
129
+ <m.span
130
+ className={cn(appBarTypography.displaySmall, "truncate w-full")}
131
+ style={{
132
+ opacity: largeTitleOpacity,
133
+ y: largeTitleY,
134
+ color: colors?.titleColor ?? APP_BAR_COLORS.title,
135
+ ...(isCentered && { textAlign: "center" }),
136
+ }}
137
+ >
138
+ {title}
139
+ </m.span>
140
+
141
+ {subtitle && (
142
+ <m.span
143
+ className={cn(appBarTypography.titleMedium, "truncate w-full")}
144
+ style={{
145
+ opacity: subtitleOpacity,
146
+ color: colors?.subtitleColor ?? APP_BAR_COLORS.subtitle,
147
+ ...(isCentered && { textAlign: "center" }),
148
+ }}
149
+ >
150
+ {subtitle}
151
+ </m.span>
152
+ )}
153
+
154
+ {headerContent && (
155
+ <m.div
156
+ className="mt-3 w-full"
157
+ style={{ opacity: headerContentOpacity }}
158
+ >
159
+ {headerContent}
160
+ </m.div>
161
+ )}
162
+ </div>
163
+ </m.header>
164
+ );
165
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @file medium-flexible-app-bar.tsx
3
+ * MD3 Expressive Medium Flexible App Bar.
4
+ *
5
+ * Two-row layout in expanded state, collapses to single row on scroll.
6
+ * Expanded height: 112px (no subtitle) / 136px (with subtitle)
7
+ * Collapsed height: 64px
8
+ * Title crossfade: HeadlineMedium (expanded) ↔ TitleLarge (collapsed)
9
+ *
10
+ * Supports `exitUntilCollapsed` scroll behavior only.
11
+ *
12
+ * @see docs/m3/app-bars/AppBarMediumFlexibleTokens.kt
13
+ */
14
+
15
+ import { m } from "motion/react";
16
+ import { cn } from "../../lib/utils";
17
+ import {
18
+ APP_BAR_COLORS,
19
+ AppBarTokens,
20
+ appBarTypography,
21
+ } from "./app-bar.tokens";
22
+ import type { FlexibleAppBarProps } from "./app-bar.types";
23
+ import { useFlexibleAppBar } from "./hooks/use-flexible-app-bar";
24
+
25
+ /**
26
+ * MD3 Expressive Medium Flexible App Bar.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * <MediumFlexibleAppBar
31
+ * title="Settings"
32
+ * subtitle="Manage your preferences"
33
+ * navigationIcon={<IconButton aria-label="Go back"><Icon>arrow_back</Icon></IconButton>}
34
+ * actions={<IconButton aria-label="More options"><Icon>more_vert</Icon></IconButton>}
35
+ * />
36
+ * ```
37
+ */
38
+ export function MediumFlexibleAppBar({
39
+ title,
40
+ subtitle,
41
+ titleAlignment = "start",
42
+ navigationIcon,
43
+ actions,
44
+ colors,
45
+ scrollElement,
46
+ headerContent,
47
+ collapsedHeight = AppBarTokens.heights.flexibleCollapsed,
48
+ expandedHeight,
49
+ className,
50
+ }: FlexibleAppBarProps) {
51
+ const resolvedExpandedHeight =
52
+ expandedHeight ??
53
+ (subtitle
54
+ ? AppBarTokens.heights.mediumFlexWithSubtitleExpanded
55
+ : AppBarTokens.heights.mediumFlexExpanded);
56
+
57
+ const {
58
+ height,
59
+ currentBg,
60
+ cssTransition,
61
+ largeTitleOpacity,
62
+ largeTitleY,
63
+ smallTitleOpacity,
64
+ subtitleOpacity,
65
+ headerContentOpacity,
66
+ } = useFlexibleAppBar({
67
+ collapsedHeight,
68
+ expandedHeight: resolvedExpandedHeight,
69
+ scrollElement,
70
+ colors,
71
+ largeTitleFadeRange: [0, 0.4],
72
+ smallTitleFadeRange: [0.5, 0.9],
73
+ subtitleFadeRange: [0, 0.3],
74
+ headerContentFadeRange: [0, 0.25],
75
+ largeTitleYRange: [0, 8],
76
+ });
77
+
78
+ const isCentered = titleAlignment === "center";
79
+
80
+ return (
81
+ <m.header
82
+ role="banner"
83
+ className={cn(
84
+ "fixed top-0 inset-x-0 z-50 flex flex-col overflow-hidden",
85
+ className,
86
+ )}
87
+ style={{ height, backgroundColor: currentBg, transition: cssTransition }}
88
+ >
89
+ <div
90
+ className="flex items-center px-1 shrink-0"
91
+ style={{ height: collapsedHeight }}
92
+ >
93
+ {navigationIcon && (
94
+ <div
95
+ className="shrink-0 flex items-center justify-center"
96
+ style={{
97
+ width: AppBarTokens.iconButtonTouchTarget,
98
+ height: AppBarTokens.iconButtonTouchTarget,
99
+ }}
100
+ >
101
+ {navigationIcon}
102
+ </div>
103
+ )}
104
+
105
+ <m.div
106
+ className={cn(
107
+ "flex-1 min-w-0",
108
+ isCentered ? "text-center" : "text-start",
109
+ !navigationIcon && "pl-4",
110
+ )}
111
+ style={{ opacity: smallTitleOpacity }}
112
+ aria-hidden="true"
113
+ >
114
+ <span
115
+ className={cn(appBarTypography.titleLarge, "truncate block")}
116
+ style={{ color: colors?.titleColor ?? APP_BAR_COLORS.title }}
117
+ >
118
+ {title}
119
+ </span>
120
+ </m.div>
121
+
122
+ {actions && <div className="flex items-center shrink-0">{actions}</div>}
123
+ </div>
124
+
125
+ <div
126
+ className={cn(
127
+ "flex flex-col flex-1 px-4 pb-3 justify-end",
128
+ isCentered ? "items-center" : "items-start",
129
+ )}
130
+ >
131
+ <m.span
132
+ className={cn(appBarTypography.headlineMedium, "truncate w-full")}
133
+ style={{
134
+ opacity: largeTitleOpacity,
135
+ y: largeTitleY,
136
+ color: colors?.titleColor ?? APP_BAR_COLORS.title,
137
+ ...(isCentered && { textAlign: "center" }),
138
+ }}
139
+ >
140
+ {title}
141
+ </m.span>
142
+
143
+ {subtitle && (
144
+ <m.span
145
+ className={cn(appBarTypography.labelLarge, "truncate w-full")}
146
+ style={{
147
+ opacity: subtitleOpacity,
148
+ color: colors?.subtitleColor ?? APP_BAR_COLORS.subtitle,
149
+ ...(isCentered && { textAlign: "center" }),
150
+ }}
151
+ >
152
+ {subtitle}
153
+ </m.span>
154
+ )}
155
+
156
+ {headerContent && (
157
+ <m.div
158
+ className="mt-2 w-full"
159
+ style={{ opacity: headerContentOpacity }}
160
+ >
161
+ {headerContent}
162
+ </m.div>
163
+ )}
164
+ </div>
165
+ </m.header>
166
+ );
167
+ }