@telefonica/mistica 17.2.0 → 17.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/css/blau.css +30 -16
  2. package/css/community/cyber.css +1491 -0
  3. package/css/esimflag.css +41 -18
  4. package/css/mistica-common.css +6 -0
  5. package/css/mistica.css +1 -1
  6. package/css/movistar.css +35 -18
  7. package/css/o2.css +32 -18
  8. package/css/telefonica.css +44 -18
  9. package/css/vivo-evolution.css +72 -20
  10. package/css/vivo.css +32 -18
  11. package/dist/accordion.css-mistica.js +10 -10
  12. package/dist/align.css-mistica.js +1 -1
  13. package/dist/autocomplete.css-mistica.js +4 -4
  14. package/dist/avatar.css-mistica.js +1 -1
  15. package/dist/badge.css-mistica.js +1 -1
  16. package/dist/box.css-mistica.js +13 -13
  17. package/dist/boxed.css-mistica.js +24 -24
  18. package/dist/boxed.d.ts +9 -0
  19. package/dist/boxed.js +18 -15
  20. package/dist/button-group.css-mistica.js +1 -1
  21. package/dist/button-layout.css-mistica.js +19 -19
  22. package/dist/button.css-mistica.js +50 -50
  23. package/dist/callout.css-mistica.js +13 -13
  24. package/dist/card-internal.css-mistica.js +22 -22
  25. package/dist/carousel.css-mistica.js +30 -30
  26. package/dist/checkbox.css-mistica.js +15 -15
  27. package/dist/chip.css-mistica.js +18 -18
  28. package/dist/circle.css-mistica.js +2 -2
  29. package/dist/community/advanced-data-card.css-mistica.js +19 -19
  30. package/dist/community/ai-card.css-mistica.js +9 -9
  31. package/dist/community/blocks.css-mistica.js +3 -3
  32. package/dist/community/cyber/example-component.css-mistica.js +1 -1
  33. package/dist/community/example-component.css-mistica.js +1 -1
  34. package/dist/community/skins/cyber-skin.d.ts +5 -0
  35. package/dist/community/skins/cyber-skin.js +289 -256
  36. package/dist/counter.css-mistica.js +6 -6
  37. package/dist/cover-hero.css-mistica.js +7 -7
  38. package/dist/credit-card-number-field.css-mistica.js +3 -3
  39. package/dist/date-field.css-mistica.js +1 -1
  40. package/dist/date-time-picker.css-mistica.js +1 -1
  41. package/dist/dialog.css-mistica.js +10 -10
  42. package/dist/divider.css-mistica.js +5 -5
  43. package/dist/double-field.css-mistica.js +2 -2
  44. package/dist/drawer.css-mistica.js +4 -4
  45. package/dist/empty-state-card.css-mistica.js +1 -1
  46. package/dist/empty-state.css-mistica.js +8 -8
  47. package/dist/fade-in.css-mistica.js +1 -1
  48. package/dist/feedback.css-mistica.js +5 -5
  49. package/dist/file-upload.css-mistica.js +10 -10
  50. package/dist/fixed-footer-layout.css-mistica.js +6 -6
  51. package/dist/form.css-mistica.js +1 -1
  52. package/dist/generated/mistica-icons/icon-panel-collapse-regular.js +165 -0
  53. package/dist/generated/mistica-icons/icon-panel-expand-regular.js +159 -0
  54. package/dist/grid-layout.css-mistica.js +3 -3
  55. package/dist/grid.css-mistica.js +120 -120
  56. package/dist/header.css-mistica.js +1 -1
  57. package/dist/hero.css-mistica.js +4 -4
  58. package/dist/horizontal-scroll.css-mistica.js +1 -1
  59. package/dist/icon-button.css-mistica.js +53 -53
  60. package/dist/icons/icon-chevron.css-mistica.js +2 -2
  61. package/dist/icons/icon-error.css-mistica.js +1 -1
  62. package/dist/image.css-mistica.js +8 -8
  63. package/dist/index.d.ts +5 -0
  64. package/dist/index.js +10 -1
  65. package/dist/inline.css-mistica.js +11 -11
  66. package/dist/list.css-mistica.js +40 -28
  67. package/dist/list.css.d.ts +2 -0
  68. package/dist/list.d.ts +3 -1
  69. package/dist/list.js +176 -165
  70. package/dist/loading-bar.css-mistica.js +1 -1
  71. package/dist/loading-screen.css-mistica.js +12 -12
  72. package/dist/logo-movistar.js +1 -1
  73. package/dist/logo.css-mistica.js +5 -5
  74. package/dist/menu.css-mistica.js +13 -13
  75. package/dist/mobile-navigation-menu.d.ts +26 -0
  76. package/dist/mobile-navigation-menu.js +145 -0
  77. package/dist/mosaic.css-mistica.js +1 -1
  78. package/dist/navigation-bar.css-mistica.js +31 -97
  79. package/dist/navigation-bar.css.d.ts +0 -24
  80. package/dist/navigation-bar.d.ts +3 -38
  81. package/dist/navigation-bar.js +399 -586
  82. package/dist/navigation-breadcrumbs.css-mistica.js +1 -1
  83. package/dist/navigation-shared.css-mistica.js +79 -0
  84. package/dist/navigation-shared.css.d.ts +26 -0
  85. package/dist/navigation-shared.d.ts +66 -0
  86. package/dist/navigation-shared.js +264 -0
  87. package/dist/package-version.js +1 -1
  88. package/dist/pagination.css-mistica.js +22 -22
  89. package/dist/pin-field.css-mistica.js +4 -4
  90. package/dist/popover.css-mistica.js +1 -1
  91. package/dist/portal.d.ts +1 -1
  92. package/dist/progress-bar.css-mistica.js +7 -7
  93. package/dist/radio-button.css-mistica.js +27 -27
  94. package/dist/rating.css-mistica.js +4 -4
  95. package/dist/responsive-layout.css-mistica.js +6 -6
  96. package/dist/screen-reader-only.css-mistica.js +1 -1
  97. package/dist/select.css-mistica.js +21 -21
  98. package/dist/sheet-action-row.css-mistica.js +1 -1
  99. package/dist/sheet-common.css-mistica.js +7 -7
  100. package/dist/sheet-info.css-mistica.js +1 -1
  101. package/dist/sidenav-bar-context.d.ts +25 -0
  102. package/dist/sidenav-bar-context.js +72 -0
  103. package/dist/sidenav-bar-data.d.ts +15 -0
  104. package/dist/sidenav-bar-data.js +38 -0
  105. package/dist/sidenav-bar-first-level-item.d.ts +42 -0
  106. package/dist/sidenav-bar-first-level-item.js +174 -0
  107. package/dist/sidenav-bar-keyboard.d.ts +13 -0
  108. package/dist/sidenav-bar-keyboard.js +314 -0
  109. package/dist/sidenav-bar-layout.css-mistica.js +28 -0
  110. package/dist/sidenav-bar-layout.css.d.ts +5 -0
  111. package/dist/sidenav-bar-layout.d.ts +22 -0
  112. package/dist/sidenav-bar-layout.js +103 -0
  113. package/dist/sidenav-bar-mobile.d.ts +24 -0
  114. package/dist/sidenav-bar-mobile.js +332 -0
  115. package/dist/sidenav-bar-motion.d.ts +32 -0
  116. package/dist/sidenav-bar-motion.js +84 -0
  117. package/dist/sidenav-bar-panel.d.ts +31 -0
  118. package/dist/sidenav-bar-panel.js +208 -0
  119. package/dist/sidenav-bar-row.d.ts +82 -0
  120. package/dist/sidenav-bar-row.js +230 -0
  121. package/dist/sidenav-bar-second-level-item.d.ts +43 -0
  122. package/dist/sidenav-bar-second-level-item.js +89 -0
  123. package/dist/sidenav-bar-section.d.ts +23 -0
  124. package/dist/sidenav-bar-section.js +167 -0
  125. package/dist/sidenav-bar-types.d.ts +192 -0
  126. package/dist/sidenav-bar-types.js +11 -0
  127. package/dist/sidenav-bar.css-mistica.js +325 -0
  128. package/dist/sidenav-bar.css.d.ts +93 -0
  129. package/dist/sidenav-bar.d.ts +102 -0
  130. package/dist/sidenav-bar.js +492 -0
  131. package/dist/skeletons.css-mistica.js +8 -8
  132. package/dist/skins/blau.d.ts +5 -0
  133. package/dist/skins/blau.js +12 -8
  134. package/dist/skins/esimflag.d.ts +5 -0
  135. package/dist/skins/esimflag.js +13 -9
  136. package/dist/skins/movistar.d.ts +5 -0
  137. package/dist/skins/movistar.js +13 -9
  138. package/dist/skins/o2.d.ts +5 -0
  139. package/dist/skins/o2.js +13 -9
  140. package/dist/skins/skin-contract.css-mistica.js +735 -731
  141. package/dist/skins/skin-contract.css.d.ts +16 -12
  142. package/dist/skins/telefonica.d.ts +5 -0
  143. package/dist/skins/telefonica.js +13 -9
  144. package/dist/skins/types/colors.d.ts +13 -6
  145. package/dist/skins/vivo-evolution.d.ts +5 -0
  146. package/dist/skins/vivo-evolution.js +14 -10
  147. package/dist/skins/vivo.d.ts +5 -0
  148. package/dist/skins/vivo.js +13 -9
  149. package/dist/skip-link.css-mistica.js +2 -2
  150. package/dist/slider.css-mistica.js +24 -24
  151. package/dist/snackbar.css-mistica.js +14 -14
  152. package/dist/spinner.css-mistica.js +1 -1
  153. package/dist/square.css-mistica.js +1 -1
  154. package/dist/stack.css-mistica.js +5 -5
  155. package/dist/stacking-group.css-mistica.js +1 -1
  156. package/dist/stepper.css-mistica.js +9 -9
  157. package/dist/switch-component.css-mistica.js +48 -42
  158. package/dist/switch-component.css.d.ts +2 -0
  159. package/dist/switch-component.js +18 -18
  160. package/dist/table.css-mistica.js +9 -9
  161. package/dist/tabs.css-mistica.js +22 -22
  162. package/dist/tag.css-mistica.js +3 -3
  163. package/dist/text-field-base.css-mistica.js +19 -19
  164. package/dist/text-field-components.css-mistica.js +10 -10
  165. package/dist/text-link.css-mistica.js +6 -6
  166. package/dist/text-tokens.d.ts +6 -0
  167. package/dist/text-tokens.js +157 -133
  168. package/dist/text.css-mistica.js +6 -6
  169. package/dist/timeline.css-mistica.js +11 -11
  170. package/dist/timer.css-mistica.js +6 -6
  171. package/dist/tooltip.css-mistica.js +2 -2
  172. package/dist/touchable.css-mistica.js +4 -4
  173. package/dist/utils/aspect-ratio-support.css-mistica.js +2 -2
  174. package/dist/video.css-mistica.js +1 -1
  175. package/dist/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
  176. package/dist-es/accordion.css-mistica.js +7 -7
  177. package/dist-es/align.css-mistica.js +1 -1
  178. package/dist-es/autocomplete.css-mistica.js +2 -2
  179. package/dist-es/avatar.css-mistica.js +1 -1
  180. package/dist-es/badge.css-mistica.js +1 -1
  181. package/dist-es/box.css-mistica.js +13 -13
  182. package/dist-es/boxed.css-mistica.js +23 -23
  183. package/dist-es/boxed.js +25 -25
  184. package/dist-es/button-group.css-mistica.js +1 -1
  185. package/dist-es/button-layout.css-mistica.js +16 -16
  186. package/dist-es/button.css-mistica.js +38 -38
  187. package/dist-es/callout.css-mistica.js +12 -12
  188. package/dist-es/card-internal.css-mistica.js +17 -17
  189. package/dist-es/carousel.css-mistica.js +10 -10
  190. package/dist-es/checkbox.css-mistica.js +13 -13
  191. package/dist-es/chip.css-mistica.js +17 -17
  192. package/dist-es/circle.css-mistica.js +2 -2
  193. package/dist-es/community/advanced-data-card.css-mistica.js +7 -7
  194. package/dist-es/community/ai-card.css-mistica.js +3 -3
  195. package/dist-es/community/blocks.css-mistica.js +2 -2
  196. package/dist-es/community/cyber/example-component.css-mistica.js +1 -1
  197. package/dist-es/community/example-component.css-mistica.js +1 -1
  198. package/dist-es/community/skins/cyber-skin.js +289 -256
  199. package/dist-es/counter.css-mistica.js +2 -2
  200. package/dist-es/cover-hero.css-mistica.js +4 -4
  201. package/dist-es/credit-card-number-field.css-mistica.js +3 -3
  202. package/dist-es/date-field.css-mistica.js +1 -1
  203. package/dist-es/date-time-picker.css-mistica.js +1 -1
  204. package/dist-es/dialog.css-mistica.js +5 -5
  205. package/dist-es/divider.css-mistica.js +5 -5
  206. package/dist-es/double-field.css-mistica.js +2 -2
  207. package/dist-es/drawer.css-mistica.js +2 -2
  208. package/dist-es/empty-state-card.css-mistica.js +1 -1
  209. package/dist-es/empty-state.css-mistica.js +7 -7
  210. package/dist-es/fade-in.css-mistica.js +1 -1
  211. package/dist-es/feedback.css-mistica.js +2 -2
  212. package/dist-es/file-upload.css-mistica.js +8 -8
  213. package/dist-es/fixed-footer-layout.css-mistica.js +3 -3
  214. package/dist-es/form.css-mistica.js +1 -1
  215. package/dist-es/generated/mistica-icons/icon-panel-collapse-regular.js +115 -0
  216. package/dist-es/generated/mistica-icons/icon-panel-expand-regular.js +109 -0
  217. package/dist-es/grid-layout.css-mistica.js +3 -3
  218. package/dist-es/grid.css-mistica.js +120 -120
  219. package/dist-es/header.css-mistica.js +1 -1
  220. package/dist-es/hero.css-mistica.js +3 -3
  221. package/dist-es/horizontal-scroll.css-mistica.js +1 -1
  222. package/dist-es/icon-button.css-mistica.js +53 -53
  223. package/dist-es/icons/icon-chevron.css-mistica.js +2 -2
  224. package/dist-es/icons/icon-error.css-mistica.js +1 -1
  225. package/dist-es/image.css-mistica.js +4 -4
  226. package/dist-es/index.js +2279 -2276
  227. package/dist-es/inline.css-mistica.js +11 -11
  228. package/dist-es/list.css-mistica.js +8 -2
  229. package/dist-es/list.js +275 -264
  230. package/dist-es/loading-bar.css-mistica.js +1 -1
  231. package/dist-es/loading-screen.css-mistica.js +5 -5
  232. package/dist-es/logo-movistar.js +1 -1
  233. package/dist-es/logo.css-mistica.js +5 -5
  234. package/dist-es/menu.css-mistica.js +13 -13
  235. package/dist-es/mobile-navigation-menu.js +90 -0
  236. package/dist-es/mosaic.css-mistica.js +1 -1
  237. package/dist-es/navigation-bar.css-mistica.js +11 -20
  238. package/dist-es/navigation-bar.js +469 -656
  239. package/dist-es/navigation-breadcrumbs.css-mistica.js +1 -1
  240. package/dist-es/navigation-shared.css-mistica.js +11 -0
  241. package/dist-es/navigation-shared.js +192 -0
  242. package/dist-es/package-version.js +1 -1
  243. package/dist-es/pagination.css-mistica.js +19 -19
  244. package/dist-es/pin-field.css-mistica.js +2 -2
  245. package/dist-es/popover.css-mistica.js +1 -1
  246. package/dist-es/progress-bar.css-mistica.js +7 -7
  247. package/dist-es/radio-button.css-mistica.js +22 -22
  248. package/dist-es/rating.css-mistica.js +3 -3
  249. package/dist-es/responsive-layout.css-mistica.js +6 -6
  250. package/dist-es/screen-reader-only.css-mistica.js +1 -1
  251. package/dist-es/select.css-mistica.js +18 -18
  252. package/dist-es/sheet-action-row.css-mistica.js +1 -1
  253. package/dist-es/sheet-common.css-mistica.js +2 -2
  254. package/dist-es/sheet-info.css-mistica.js +1 -1
  255. package/dist-es/sidenav-bar-context.js +14 -0
  256. package/dist-es/sidenav-bar-data.js +12 -0
  257. package/dist-es/sidenav-bar-first-level-item.js +124 -0
  258. package/dist-es/sidenav-bar-keyboard.js +253 -0
  259. package/dist-es/sidenav-bar-layout.css-mistica.js +2 -0
  260. package/dist-es/sidenav-bar-layout.js +86 -0
  261. package/dist-es/sidenav-bar-mobile.js +277 -0
  262. package/dist-es/sidenav-bar-motion.js +26 -0
  263. package/dist-es/sidenav-bar-panel.js +146 -0
  264. package/dist-es/sidenav-bar-row.js +208 -0
  265. package/dist-es/sidenav-bar-second-level-item.js +72 -0
  266. package/dist-es/sidenav-bar-section.js +112 -0
  267. package/dist-es/sidenav-bar-types.js +2 -0
  268. package/dist-es/sidenav-bar.css-mistica.js +74 -0
  269. package/dist-es/sidenav-bar.js +429 -0
  270. package/dist-es/skeletons.css-mistica.js +7 -7
  271. package/dist-es/skins/blau.js +12 -8
  272. package/dist-es/skins/esimflag.js +13 -9
  273. package/dist-es/skins/movistar.js +13 -9
  274. package/dist-es/skins/o2.js +13 -9
  275. package/dist-es/skins/skin-contract.css-mistica.js +735 -731
  276. package/dist-es/skins/telefonica.js +13 -9
  277. package/dist-es/skins/vivo-evolution.js +14 -10
  278. package/dist-es/skins/vivo.js +13 -9
  279. package/dist-es/skip-link.css-mistica.js +2 -2
  280. package/dist-es/slider.css-mistica.js +20 -20
  281. package/dist-es/snackbar.css-mistica.js +5 -5
  282. package/dist-es/spinner.css-mistica.js +1 -1
  283. package/dist-es/square.css-mistica.js +1 -1
  284. package/dist-es/stack.css-mistica.js +5 -5
  285. package/dist-es/stacking-group.css-mistica.js +1 -1
  286. package/dist-es/stepper.css-mistica.js +3 -3
  287. package/dist-es/style.css +1 -1
  288. package/dist-es/switch-component.css-mistica.js +37 -37
  289. package/dist-es/switch-component.js +26 -26
  290. package/dist-es/table.css-mistica.js +9 -9
  291. package/dist-es/tabs.css-mistica.js +19 -19
  292. package/dist-es/tag.css-mistica.js +2 -2
  293. package/dist-es/text-field-base.css-mistica.js +17 -17
  294. package/dist-es/text-field-components.css-mistica.js +4 -4
  295. package/dist-es/text-link.css-mistica.js +6 -6
  296. package/dist-es/text-tokens.js +82 -67
  297. package/dist-es/text.css-mistica.js +6 -6
  298. package/dist-es/timeline.css-mistica.js +10 -10
  299. package/dist-es/timer.css-mistica.js +6 -6
  300. package/dist-es/tooltip.css-mistica.js +2 -2
  301. package/dist-es/touchable.css-mistica.js +2 -2
  302. package/dist-es/utils/aspect-ratio-support.css-mistica.js +2 -2
  303. package/dist-es/video.css-mistica.js +1 -1
  304. package/dist-es/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
  305. package/package.json +3 -2
  306. package/src/boxed.css.ts +1 -0
  307. package/src/boxed.tsx +18 -5
  308. package/src/callout.css.ts +4 -2
  309. package/src/community/skins/cyber-skin.tsx +127 -439
  310. package/src/index.tsx +14 -0
  311. package/src/list.css.ts +36 -1
  312. package/src/list.tsx +370 -344
  313. package/src/logo-movistar.tsx +1 -1
  314. package/src/mobile-navigation-menu.tsx +168 -0
  315. package/src/navigation-bar.css.ts +2 -267
  316. package/src/navigation-bar.tsx +77 -370
  317. package/src/navigation-shared.css.ts +289 -0
  318. package/src/navigation-shared.tsx +262 -0
  319. package/src/package-version.tsx +1 -1
  320. package/src/portal.tsx +1 -1
  321. package/src/sidenav-bar-context.tsx +41 -0
  322. package/src/sidenav-bar-data.ts +49 -0
  323. package/src/sidenav-bar-first-level-item.tsx +203 -0
  324. package/src/sidenav-bar-keyboard.tsx +393 -0
  325. package/src/sidenav-bar-layout.css.ts +89 -0
  326. package/src/sidenav-bar-layout.tsx +61 -0
  327. package/src/sidenav-bar-mobile.tsx +302 -0
  328. package/src/sidenav-bar-motion.tsx +70 -0
  329. package/src/sidenav-bar-panel.tsx +269 -0
  330. package/src/sidenav-bar-row.tsx +272 -0
  331. package/src/sidenav-bar-second-level-item.tsx +99 -0
  332. package/src/sidenav-bar-section.tsx +98 -0
  333. package/src/sidenav-bar-types.ts +224 -0
  334. package/src/sidenav-bar.css.ts +1018 -0
  335. package/src/sidenav-bar.tsx +821 -0
  336. package/src/skins/blau.tsx +18 -8
  337. package/src/skins/esimflag.tsx +19 -9
  338. package/src/skins/movistar.tsx +19 -9
  339. package/src/skins/o2.tsx +19 -9
  340. package/src/skins/skin-contract.css.ts +2 -0
  341. package/src/skins/telefonica.tsx +19 -9
  342. package/src/skins/types/colors.tsx +14 -6
  343. package/src/skins/vivo-evolution.tsx +20 -10
  344. package/src/skins/vivo.tsx +19 -9
  345. package/src/switch-component.css.ts +20 -0
  346. package/src/switch-component.tsx +9 -1
  347. package/src/text-tokens.tsx +24 -0
@@ -0,0 +1,203 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ import {CSSTransition} from 'react-transition-group';
4
+ import * as styles from './sidenav-bar.css';
5
+ import {CONTENT_DURATION_MS} from './sidenav-bar.css';
6
+ import {useSidenavBarContext} from './sidenav-bar-context';
7
+ import {SidenavDialogPanel} from './sidenav-bar-panel';
8
+ import {SidenavRow, useSidenavNavigation} from './sidenav-bar-row';
9
+ import {renderSidenavSecondLevelItem} from './sidenav-bar-second-level-item';
10
+
11
+ import type {DataAttributes} from './utils/types';
12
+ import type {SidenavAsset, SidenavSecondLevelItem as SidenavSecondLevelItemData} from './sidenav-bar-types';
13
+
14
+ type SidenavFirstLevelItemProps = {
15
+ /** Unique identifier for selection tracking. */
16
+ id: string;
17
+ /** Display text. A text longer than the row wraps over several lines, and it never truncates. */
18
+ label: string;
19
+ /** Icon component or element. The collapsed rail shows nothing else of a first-level item. */
20
+ asset: SidenavAsset;
21
+ /** Show asset when expanded (not collapsed). @default true */
22
+ showAssetWhenExpanded?: boolean;
23
+ /** Custom content on the right side (e.g., Badge). */
24
+ rightSlot?: React.ReactNode;
25
+ /**
26
+ * True when the item sits directly in the body list, which already gave it a list item. A stand-alone
27
+ * entry carries that list item on a wrapper of its own, so this item renders none.
28
+ */
29
+ standalone?: boolean;
30
+ /**
31
+ * Second-level items. An item with second-level items cannot navigate: it lists them in place, in a dialog
32
+ * panel over the collapsed rail, or in the second column, which the bar renders.
33
+ */
34
+ secondLevelItems?: ReadonlyArray<SidenavSecondLevelItemData>;
35
+ /** Initial open state of the children. @default false */
36
+ defaultOpen?: boolean;
37
+ onPress?: () => void;
38
+ href?: string;
39
+ to?: string;
40
+ /** Open link in new tab. @default false */
41
+ newTab?: boolean;
42
+ /** Called after navigation. */
43
+ onNavigate?: () => void | Promise<void>;
44
+ dataAttributes?: DataAttributes;
45
+ };
46
+
47
+ /**
48
+ * A first-level item. It either navigates, or it owns the second-level items and only opens and closes
49
+ * them. It opens them in place when the rail is expanded, in a floating panel when the rail is collapsed,
50
+ * and in the second column in double panel mode.
51
+ */
52
+ const SidenavFirstLevelItem = ({
53
+ id,
54
+ label,
55
+ asset,
56
+ showAssetWhenExpanded = true,
57
+ rightSlot,
58
+ standalone = false,
59
+ secondLevelItems,
60
+ defaultOpen,
61
+ dataAttributes,
62
+ ...navigationProps
63
+ }: SidenavFirstLevelItemProps): JSX.Element => {
64
+ const {
65
+ collapsed,
66
+ collapseState,
67
+ isMotionOff,
68
+ doublePanel,
69
+ panelOpenForItemId,
70
+ setPanelOpenForItemId,
71
+ containerRef,
72
+ selectedItemId,
73
+ } = useSidenavBarContext();
74
+ const navigation = useSidenavNavigation(id, navigationProps);
75
+
76
+ const hasChildren = !!secondLevelItems?.length;
77
+ const isItemSelected = selectedItemId === id;
78
+ const hasDescendantSelected =
79
+ !!selectedItemId && !!secondLevelItems?.some((item) => item.id === selectedItemId);
80
+
81
+ const [open, setOpen] = React.useState(Boolean(defaultOpen));
82
+ // The dialog panel renders in a portal, so the trigger points at it with `aria-controls`.
83
+ const dialogPanelId = React.useId();
84
+ const labelId = React.useId();
85
+ const accordionRef = React.useRef<HTMLDivElement>(null);
86
+
87
+ const isPanelOpen = hasChildren && panelOpenForItemId === id;
88
+ const isPanelMode = hasChildren && (collapsed || doublePanel);
89
+ const isOpen = hasChildren && !collapsed && !isPanelMode && open;
90
+ // In double panel mode the bar renders the panel as its second column; the dialog panel renders here.
91
+ const isDialogMode = isPanelMode && !doublePanel;
92
+ const isPanelOpenForAnotherItem = panelOpenForItemId && panelOpenForItemId !== id;
93
+
94
+ // Never two highlighted rows: a closed parent flags a hidden selected descendant, an open parent
95
+ // leaves the background to the selected child, and an open panel takes it from every other parent.
96
+ const showBackground =
97
+ isItemSelected || isPanelOpen || (hasDescendantSelected && !isOpen && !isPanelOpenForAnotherItem);
98
+
99
+ // Keyed on the descendant id, not on a boolean, so that a selection change between two siblings
100
+ // reopens a parent the user closed. It only opens, never closes.
101
+ const selectedDescendantId = hasDescendantSelected ? selectedItemId : null;
102
+ React.useEffect(() => {
103
+ if (selectedDescendantId !== null) {
104
+ setOpen(true);
105
+ }
106
+ }, [selectedDescendantId]);
107
+
108
+ React.useEffect(() => {
109
+ if (process.env.NODE_ENV !== 'production' && !asset) {
110
+ console.error(
111
+ `SidenavBar item "${label}" is a first-level item without an asset, so the collapsed sidenav shows nothing of it. ` +
112
+ `Pass an asset, and hide it with showAssetWhenExpanded if you do not want it next to the label.`
113
+ );
114
+ }
115
+ }, [asset, label]);
116
+
117
+ // The rail drops every tooltip while the dialog panel floats over it. Beside a second column only the
118
+ // owner item drops its own, because the column shows its label as a title. The tooltip arrives once the
119
+ // rail rests collapsed and leaves once it rests expanded: its wrapper replaces the row node, and a
120
+ // replaced row drops its transition.
121
+ const isRestingCollapsed = collapseState === 'collapsed' || collapseState === 'expanding';
122
+ const showTooltip = isRestingCollapsed && (doublePanel ? !isPanelOpen : !panelOpenForItemId);
123
+
124
+ const handlePress = () => {
125
+ if (isPanelMode) {
126
+ setPanelOpenForItemId(isPanelOpen ? null : id);
127
+ } else {
128
+ setOpen((prev) => !prev);
129
+ }
130
+ };
131
+
132
+ const row = (
133
+ <SidenavRow
134
+ id={id}
135
+ label={label}
136
+ asset={asset}
137
+ // The collapsed rail shows the asset alone, so it keeps it whatever the prop says.
138
+ showAsset={collapsed || showAssetWhenExpanded}
139
+ rightSlot={rightSlot}
140
+ labelId={labelId}
141
+ showBackground={showBackground}
142
+ // The accent belongs to the selected item only, never to a parent of it.
143
+ showAccent={isItemSelected}
144
+ current={isItemSelected || hasDescendantSelected}
145
+ chevron={hasChildren ? {rotated: isOpen, direction: doublePanel ? 'right' : 'down'} : undefined}
146
+ placement="rail"
147
+ tooltip={showTooltip}
148
+ navigation={hasChildren ? undefined : navigation}
149
+ toggle={
150
+ hasChildren
151
+ ? {
152
+ onPress: handlePress,
153
+ expanded: isPanelMode ? isPanelOpen : isOpen,
154
+ controls: isPanelOpen && isDialogMode ? dialogPanelId : undefined,
155
+ }
156
+ : undefined
157
+ }
158
+ dataAttributes={dataAttributes}
159
+ />
160
+ );
161
+
162
+ // The row and its accordion share one list item. A stand-alone first-level entry already sits in
163
+ // a list item of the body list, so this one steps aside there.
164
+ return (
165
+ <div role={standalone ? undefined : 'listitem'}>
166
+ {row}
167
+ {hasChildren && (
168
+ <CSSTransition
169
+ in={isOpen}
170
+ timeout={isMotionOff ? 0 : CONTENT_DURATION_MS}
171
+ nodeRef={accordionRef}
172
+ classNames={styles.accordionTransitionClasses}
173
+ mountOnEnter
174
+ unmountOnExit
175
+ >
176
+ <div
177
+ className={styles.accordionContainer}
178
+ ref={accordionRef}
179
+ // ArrowLeft on a child reads it to move the focus back to the trigger.
180
+ data-sidenav-accordion-for={id}
181
+ >
182
+ <div className={styles.accordion}>
183
+ <div className={styles.accordionRows} role="list" aria-labelledby={labelId}>
184
+ {secondLevelItems?.map((item) =>
185
+ renderSidenavSecondLevelItem(item, {placement: 'accordion'})
186
+ )}
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </CSSTransition>
191
+ )}
192
+ {isPanelOpen && isDialogMode && (
193
+ <SidenavDialogPanel id={dialogPanelId} itemId={id} label={label} containerRef={containerRef}>
194
+ {secondLevelItems?.map((item) =>
195
+ renderSidenavSecondLevelItem(item, {placement: 'panel'})
196
+ )}
197
+ </SidenavDialogPanel>
198
+ )}
199
+ </div>
200
+ );
201
+ };
202
+
203
+ export {SidenavFirstLevelItem};
@@ -0,0 +1,393 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * Key map of the spec:
6
+ * ArrowUp / ArrowDown move between items
7
+ * Home / End first / last item
8
+ * ArrowRight expand a closed parent, or step into its open group
9
+ * ArrowLeft collapse an open parent, or move from a child to its parent
10
+ * Tab an item, then the children it opened, then the next item
11
+ * Escape close the dialog panel (handled in sidenav-bar-panel.tsx)
12
+ *
13
+ * Arrows stay on one level. Tab follows the reading order of the spec, which the document does not
14
+ * carry: the dialog panel lives in a portal and the second column comes after the whole rail.
15
+ */
16
+
17
+ const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])';
18
+
19
+ const escapeAttributeValue = (value: string): string =>
20
+ typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(value) : value.replace(/"/g, '\\"');
21
+
22
+ const toFocusables = (rows: Array<HTMLElement>): Array<HTMLElement> =>
23
+ rows
24
+ .map((row) => row.querySelector<HTMLElement>(FOCUSABLE_SELECTOR))
25
+ .filter((element): element is HTMLElement => element !== null);
26
+
27
+ const getItemFocusables = (root: HTMLElement | null): Array<HTMLElement> =>
28
+ root ? toFocusables(Array.from(root.querySelectorAll<HTMLElement>('[data-sidenav-item-id]'))) : [];
29
+
30
+ // The second column rows share the landmark. Without this filter they would end the rail sequence.
31
+ const getRailFocusables = (root: HTMLElement | null): Array<HTMLElement> => {
32
+ if (!root) {
33
+ return [];
34
+ }
35
+ const rows = Array.from(root.querySelectorAll<HTMLElement>('[data-sidenav-item-id]')).filter(
36
+ (row) => !row.closest('[data-sidenav-double-panel]')
37
+ );
38
+ return toFocusables(rows);
39
+ };
40
+
41
+ const getRailFocusable = (itemId: string, container: HTMLElement): HTMLElement | null => {
42
+ const row = container.querySelector<HTMLElement>(
43
+ `[data-sidenav-item-id="${escapeAttributeValue(itemId)}"]`
44
+ );
45
+ return row?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR) ?? null;
46
+ };
47
+
48
+ const getRailFocusableAfter = (triggerItemId: string, container: HTMLElement): HTMLElement | null => {
49
+ const trigger = getRailFocusable(triggerItemId, container);
50
+ if (!trigger) {
51
+ return null;
52
+ }
53
+ const rail = getRailFocusables(container);
54
+ return rail[rail.indexOf(trigger) + 1] ?? null;
55
+ };
56
+
57
+ const getOpenDialogPanel = (trigger: HTMLElement): HTMLElement | null => {
58
+ if (trigger.getAttribute('aria-expanded') !== 'true') {
59
+ return null;
60
+ }
61
+ const row = trigger.closest('[data-sidenav-item-id]');
62
+ const itemId = row?.getAttribute('data-sidenav-item-id');
63
+ if (!itemId) {
64
+ return null;
65
+ }
66
+ return document.querySelector<HTMLElement>(
67
+ `[data-sidenav-dialog-panel="${escapeAttributeValue(itemId)}"]`
68
+ );
69
+ };
70
+
71
+ const getOpenDoublePanel = (trigger: HTMLElement, container: HTMLElement): HTMLElement | null => {
72
+ const itemId = trigger.closest('[data-sidenav-item-id]')?.getAttribute('data-sidenav-item-id');
73
+ if (!itemId) {
74
+ return null;
75
+ }
76
+ return container.querySelector<HTMLElement>(
77
+ `[data-sidenav-double-panel="${escapeAttributeValue(itemId)}"]`
78
+ );
79
+ };
80
+
81
+ const getOpenAccordion = (trigger: HTMLElement, container: HTMLElement): HTMLElement | null => {
82
+ const itemId = trigger.closest('[data-sidenav-item-id]')?.getAttribute('data-sidenav-item-id');
83
+ if (!itemId) {
84
+ return null;
85
+ }
86
+ return container.querySelector<HTMLElement>(
87
+ `[data-sidenav-accordion-for="${escapeAttributeValue(itemId)}"]`
88
+ );
89
+ };
90
+
91
+ const getParentTrigger = (child: HTMLElement, container: HTMLElement): HTMLElement | null => {
92
+ const accordion = child.closest('[data-sidenav-accordion-for]');
93
+ const parentId = accordion?.getAttribute('data-sidenav-accordion-for');
94
+ if (!parentId) {
95
+ return null;
96
+ }
97
+ const parentRow = container.querySelector<HTMLElement>(
98
+ `[data-sidenav-item-id="${escapeAttributeValue(parentId)}"]`
99
+ );
100
+ return parentRow?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR) ?? null;
101
+ };
102
+
103
+ const useSidenavRailKeyboard = (
104
+ containerRef: React.RefObject<HTMLElement | null>
105
+ ): ((event: React.KeyboardEvent) => void) =>
106
+ React.useCallback(
107
+ (event: React.KeyboardEvent) => {
108
+ const container = containerRef.current;
109
+ if (!container) {
110
+ return;
111
+ }
112
+ const active = document.activeElement as HTMLElement | null;
113
+ if (!active) {
114
+ return;
115
+ }
116
+
117
+ const column = active.closest<HTMLElement>('[data-sidenav-double-panel]');
118
+ if (column) {
119
+ const columnItemId = column.getAttribute('data-sidenav-double-panel') ?? '';
120
+ const trigger = getRailFocusable(columnItemId, container);
121
+ const columnItems = getItemFocusables(column);
122
+ const columnIndex = columnItems.indexOf(active);
123
+
124
+ switch (event.key) {
125
+ case 'ArrowDown':
126
+ event.preventDefault();
127
+ columnItems[Math.min(columnIndex + 1, columnItems.length - 1)]?.focus();
128
+ return;
129
+ // The column is not a floating panel, so it stays open when the focus leaves it.
130
+ case 'ArrowUp':
131
+ case 'ArrowLeft':
132
+ event.preventDefault();
133
+ if (event.key === 'ArrowUp' && columnIndex > 0) {
134
+ columnItems[columnIndex - 1].focus();
135
+ return;
136
+ }
137
+ trigger?.focus();
138
+ return;
139
+ case 'ArrowRight':
140
+ event.preventDefault();
141
+ return;
142
+ case 'Home':
143
+ event.preventDefault();
144
+ columnItems[0]?.focus();
145
+ return;
146
+ case 'End':
147
+ event.preventDefault();
148
+ columnItems[columnItems.length - 1]?.focus();
149
+ return;
150
+ case 'Tab': {
151
+ event.preventDefault();
152
+ if (event.shiftKey) {
153
+ (columnIndex > 0 ? columnItems[columnIndex - 1] : trigger)?.focus();
154
+ return;
155
+ }
156
+ const next =
157
+ columnIndex < columnItems.length - 1
158
+ ? columnItems[columnIndex + 1]
159
+ : getRailFocusableAfter(columnItemId, container);
160
+ next?.focus();
161
+ return;
162
+ }
163
+ default:
164
+ return;
165
+ }
166
+ }
167
+
168
+ // The collapse action in the header also has `aria-expanded`. ArrowLeft must not toggle it.
169
+ if (!active.closest('[data-sidenav-item-id]')) {
170
+ return;
171
+ }
172
+
173
+ const items = getRailFocusables(container);
174
+ const index = items.indexOf(active);
175
+
176
+ switch (event.key) {
177
+ case 'ArrowDown': {
178
+ event.preventDefault();
179
+ if (index >= 0 && index < items.length - 1) {
180
+ items[index + 1].focus();
181
+ }
182
+ return;
183
+ }
184
+ case 'ArrowUp': {
185
+ event.preventDefault();
186
+ if (index > 0) {
187
+ items[index - 1].focus();
188
+ }
189
+ return;
190
+ }
191
+ case 'Home': {
192
+ event.preventDefault();
193
+ items[0]?.focus();
194
+ return;
195
+ }
196
+ case 'End': {
197
+ event.preventDefault();
198
+ items[items.length - 1]?.focus();
199
+ return;
200
+ }
201
+ case 'ArrowRight': {
202
+ if (active.getAttribute('aria-expanded') === 'false') {
203
+ event.preventDefault();
204
+ // The effect that watches the open state focuses the first child.
205
+ active.click();
206
+ return;
207
+ }
208
+ const dialogPanel = getOpenDialogPanel(active);
209
+ if (dialogPanel) {
210
+ event.preventDefault();
211
+ getItemFocusables(dialogPanel)[0]?.focus();
212
+ return;
213
+ }
214
+ const column = getOpenDoublePanel(active, container);
215
+ if (column) {
216
+ event.preventDefault();
217
+ getItemFocusables(column)[0]?.focus();
218
+ return;
219
+ }
220
+ const accordion = getOpenAccordion(active, container);
221
+ if (accordion) {
222
+ event.preventDefault();
223
+ getItemFocusables(accordion)[0]?.focus();
224
+ }
225
+ return;
226
+ }
227
+ case 'ArrowLeft': {
228
+ if (active.getAttribute('aria-expanded') === 'true') {
229
+ event.preventDefault();
230
+ active.click();
231
+ return;
232
+ }
233
+ const parent = getParentTrigger(active, container);
234
+ if (parent) {
235
+ event.preventDefault();
236
+ parent.focus();
237
+ }
238
+ return;
239
+ }
240
+ case 'Tab': {
241
+ if (!event.shiftKey) {
242
+ const panel = getOpenDialogPanel(active) ?? getOpenDoublePanel(active, container);
243
+ if (panel) {
244
+ event.preventDefault();
245
+ getItemFocusables(panel)[0]?.focus();
246
+ }
247
+ return;
248
+ }
249
+ const previous = index > 0 ? items[index - 1] : null;
250
+ if (!previous) {
251
+ return;
252
+ }
253
+ const previousPanel =
254
+ getOpenDialogPanel(previous) ?? getOpenDoublePanel(previous, container);
255
+ const lastChild = getItemFocusables(previousPanel).pop();
256
+ if (lastChild) {
257
+ event.preventDefault();
258
+ lastChild.focus();
259
+ }
260
+ return;
261
+ }
262
+ default:
263
+ return;
264
+ }
265
+ },
266
+ [containerRef]
267
+ );
268
+
269
+ type DialogPanelKeyboardArgs = {
270
+ panelElement: HTMLElement | null;
271
+ containerRef: React.RefObject<HTMLElement | null>;
272
+ itemId: string;
273
+ /** A hidden element takes no focus, so the panel must be positioned first. */
274
+ isPositioned: boolean;
275
+ onClose: () => void;
276
+ };
277
+
278
+ const useDialogPanelKeyboard = ({
279
+ panelElement,
280
+ containerRef,
281
+ itemId,
282
+ isPositioned,
283
+ onClose,
284
+ }: DialogPanelKeyboardArgs): void => {
285
+ const triggerRef = React.useRef<HTMLElement | null>(null);
286
+ // Read through a ref, so a new callback does not re-attach the listener.
287
+ const onCloseRef = React.useRef(onClose);
288
+ onCloseRef.current = onClose;
289
+
290
+ React.useEffect(() => {
291
+ const row = containerRef.current?.querySelector(
292
+ `[data-sidenav-item-id="${escapeAttributeValue(itemId)}"]`
293
+ );
294
+ triggerRef.current = row?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR) ?? null;
295
+ }, [containerRef, itemId, panelElement]);
296
+
297
+ // Open and close both replace the trigger row: the collapsed rail drops and re-adds its tooltips.
298
+ // The focus falls to the body, so this re-reads the trigger one frame later and focuses it again.
299
+ const restoreFocusToTrigger = React.useCallback(() => {
300
+ const container = containerRef.current;
301
+ if (!container) {
302
+ return;
303
+ }
304
+ const restoreFocus = () => {
305
+ const active = document.activeElement;
306
+ if (active && active !== document.body) {
307
+ return;
308
+ }
309
+ const row = container.querySelector(`[data-sidenav-item-id="${escapeAttributeValue(itemId)}"]`);
310
+ row?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR)?.focus();
311
+ };
312
+ if (typeof requestAnimationFrame === 'function') {
313
+ requestAnimationFrame(restoreFocus);
314
+ } else {
315
+ restoreFocus();
316
+ }
317
+ }, [containerRef, itemId]);
318
+
319
+ // The panel lives in a portal, so a screen reader would not reach it from the trigger.
320
+ React.useEffect(() => {
321
+ if (!panelElement || !isPositioned) {
322
+ return;
323
+ }
324
+ getItemFocusables(panelElement)[0]?.focus();
325
+ }, [panelElement, isPositioned]);
326
+
327
+ React.useEffect(() => () => restoreFocusToTrigger(), [restoreFocusToTrigger]);
328
+
329
+ React.useEffect(() => {
330
+ if (!panelElement) {
331
+ return;
332
+ }
333
+ const handleKeyDown = (event: KeyboardEvent) => {
334
+ const focusables = getItemFocusables(panelElement);
335
+ if (focusables.length === 0) {
336
+ return;
337
+ }
338
+ const active = document.activeElement as HTMLElement | null;
339
+ const index = active ? focusables.indexOf(active) : -1;
340
+
341
+ switch (event.key) {
342
+ case 'ArrowDown':
343
+ event.preventDefault();
344
+ focusables[Math.min(index + 1, focusables.length - 1)]?.focus();
345
+ return;
346
+ // Closing the panel unmounts it, and the restore effect returns the focus to the trigger.
347
+ case 'ArrowLeft':
348
+ event.preventDefault();
349
+ onCloseRef.current();
350
+ return;
351
+ case 'ArrowUp':
352
+ event.preventDefault();
353
+ if (index <= 0) {
354
+ onCloseRef.current();
355
+ return;
356
+ }
357
+ focusables[index - 1].focus();
358
+ return;
359
+ case 'Home':
360
+ event.preventDefault();
361
+ focusables[0].focus();
362
+ return;
363
+ case 'End':
364
+ event.preventDefault();
365
+ focusables[focusables.length - 1].focus();
366
+ return;
367
+ case 'Tab': {
368
+ event.preventDefault();
369
+ if (event.shiftKey) {
370
+ (index > 0 ? focusables[index - 1] : triggerRef.current)?.focus();
371
+ return;
372
+ }
373
+ if (index < focusables.length - 1) {
374
+ focusables[index + 1].focus();
375
+ return;
376
+ }
377
+ const container = containerRef.current;
378
+ if (container) {
379
+ getRailFocusableAfter(itemId, container)?.focus();
380
+ }
381
+ return;
382
+ }
383
+ default:
384
+ return;
385
+ }
386
+ };
387
+
388
+ panelElement.addEventListener('keydown', handleKeyDown);
389
+ return () => panelElement.removeEventListener('keydown', handleKeyDown);
390
+ }, [panelElement, containerRef, itemId]);
391
+ };
392
+
393
+ export {useSidenavRailKeyboard, useDialogPanelKeyboard, getItemFocusables};
@@ -0,0 +1,89 @@
1
+ import {createVar, fallbackVar, style} from '@vanilla-extract/css';
2
+ import {sprinkles} from './sprinkles.css';
3
+ import * as mq from './media-queries.css';
4
+
5
+ // The height of a persistent band above the layout (a fixed or sticky top header). The rail sticks
6
+ // below that band and subtracts it from its viewport height, so the sidenav does not slide under the
7
+ // header and does not overflow the bottom of the viewport. A header that scrolls away with the document
8
+ // needs no offset: the rail sticks at the top of the viewport once the header has left it.
9
+ export const topOffsetVar = createVar();
10
+
11
+ const topOffset = fallbackVar(topOffsetVar, '0px');
12
+
13
+ // A mobile or tablet sidenav is a top bar, and not a column, so the two regions stack there. The spec
14
+ // gives "N/A" for the width of the sidenav on both breakpoints.
15
+ // The layout does not own a scroll region: the document scrolls, and the rail sticks to the top of the
16
+ // viewport (see `sidenav`). A single, isolated `overflow: auto` content region has no keyboard access when
17
+ // its content holds no focusable element, so axe reports `scrollable-region-focusable`. Every other page
18
+ // layout of the repo scrolls the document as well.
19
+ export const container = style([
20
+ sprinkles({
21
+ display: 'flex',
22
+ }),
23
+ {
24
+ width: '100%',
25
+ minWidth: 0,
26
+ gap: 0,
27
+ boxSizing: 'border-box',
28
+
29
+ '@media': {
30
+ [mq.tabletOrSmaller]: {
31
+ flexDirection: 'column',
32
+ },
33
+ },
34
+ },
35
+ ]);
36
+
37
+ // The rail sticks to the top of the viewport and keeps the height of the viewport, so it stays in place
38
+ // while the document scrolls the content beside it. `alignSelf: flex-start` stops the flex row from
39
+ // stretching the rail to the height of a tall content column, which would leave the sticky rail with no
40
+ // room to travel. On tablet or smaller the rail is a top bar that stacks above the content, so it flows
41
+ // with the document instead.
42
+ export const sidenav = style([
43
+ sprinkles({
44
+ display: 'flex',
45
+ flexDirection: 'column',
46
+ alignItems: 'stretch',
47
+ }),
48
+ {
49
+ flexShrink: 0,
50
+ width: 'auto',
51
+ position: 'sticky',
52
+ top: topOffset,
53
+ alignSelf: 'flex-start',
54
+ height: `calc(100vh - ${topOffset})`,
55
+ margin: 0,
56
+ padding: 0,
57
+ // Firefox keeps a cached layer for a sticky column and does not always invalidate its right edge
58
+ // when a child changes: toggling the boxed border leaves the 1px edge half painted until the next
59
+ // repaint. A dedicated compositor layer makes Firefox repaint the column as one unit and removes the
60
+ // artifact. The only fixed descendant (the collapsed panel) renders through a Portal, so this
61
+ // layer does not change its containing block.
62
+ willChange: 'transform',
63
+
64
+ '@media': {
65
+ [mq.tabletOrSmaller]: {
66
+ position: 'static',
67
+ width: '100%',
68
+ height: 'auto',
69
+ alignSelf: 'auto',
70
+ },
71
+ },
72
+ },
73
+ ]);
74
+
75
+ export const content = style([
76
+ sprinkles({
77
+ flex: 1,
78
+ minWidth: 0,
79
+ }),
80
+ {
81
+ margin: 0,
82
+ padding: 0,
83
+ },
84
+ ]);
85
+
86
+ export const centeredResponsiveLayout = style({
87
+ display: 'flex',
88
+ flexDirection: 'column',
89
+ });