|
| 1 | +<template> |
| 2 | + <NavigationMenuRoot |
| 3 | + v-if="menuData" |
| 4 | + :disable-hover-trigger="true" |
| 5 | + class="NavigationMenuRoot" |
| 6 | + > |
| 7 | + <NavigationMenuList class="NavigationMenuList"> |
| 8 | + <NavigationMenuItem value="Groups"> |
| 9 | + <NavigationMenuTrigger class="dropdown-toggle NavigationMenuTrigger"> |
| 10 | + Groups |
| 11 | + </NavigationMenuTrigger> |
| 12 | + <NavigationMenuContent class="NavigationMenuContent"> |
| 13 | + <NavigationMenuSub default-value="0"> |
| 14 | + <NavigationMenuList class="NavigationMenu-NoList"> |
| 15 | + <NavigationMenuItem |
| 16 | + v-for="(menuItem, menuItemIndex) in menuData" |
| 17 | + :key="menuItemIndex" |
| 18 | + :val="menuItemIndex" |
| 19 | + > |
| 20 | + <NavigationMenuTrigger class="NavigationMenuLevel1"> |
| 21 | + {{ menuItem.label }} |
| 22 | + </NavigationMenuTrigger> |
| 23 | + <NavigationMenuContent> |
| 24 | + <fieldset |
| 25 | + v-for="(childrenItems, groupType, childrenIndex) in menuItem.children" |
| 26 | + :key="childrenIndex" |
| 27 | + > |
| 28 | + <legend class="NavigationMenuLegend"> |
| 29 | + {{ groupType }} |
| 30 | + </legend> |
| 31 | + <ul class="NavigationMenu-NoList"> |
| 32 | + <li |
| 33 | + v-for="(childrenItem, childrenItemIndex) in childrenItems" |
| 34 | + :key="childrenItemIndex" |
| 35 | + > |
| 36 | + <NavigationMenuLink as-child> |
| 37 | + <a :href="childrenItem.url" class="NavigationMenuLevel2"> |
| 38 | + {{ childrenItem.acronym }} |
| 39 | + — |
| 40 | + {{ childrenItem.name }} |
| 41 | + </a> |
| 42 | + </NavigationMenuLink> |
| 43 | + </li> |
| 44 | + </ul> |
| 45 | + </fieldset> |
| 46 | + </NavigationMenuContent> |
| 47 | + </NavigationMenuItem> |
| 48 | + </NavigationMenuList> |
| 49 | + </NavigationMenuSub> |
| 50 | + </NavigationMenuContent> |
| 51 | + </NavigationMenuItem> |
| 52 | + </NavigationMenuList> |
| 53 | + </NavigationMenuRoot> |
| 54 | +</template> |
| 55 | + |
| 56 | +<script setup lang="js"> |
| 57 | +import { |
| 58 | + NavigationMenuContent, |
| 59 | + NavigationMenuIndicator, |
| 60 | + NavigationMenuItem, |
| 61 | + NavigationMenuLink, |
| 62 | + NavigationMenuList, |
| 63 | + NavigationMenuRoot, |
| 64 | + NavigationMenuSub, |
| 65 | + NavigationMenuTrigger, |
| 66 | + NavigationMenuViewport, |
| 67 | +} from 'reka-ui' |
| 68 | +import { onMounted, ref } from 'vue' |
| 69 | +import { groupBy } from 'lodash-es' |
| 70 | +
|
| 71 | +
|
| 72 | +// type GroupMenuData = Record<string, { |
| 73 | +// "acronym": string |
| 74 | +// "name": string |
| 75 | +// "type": string |
| 76 | +// "url": string |
| 77 | +// }[]> |
| 78 | +
|
| 79 | +// type MenuItem = { |
| 80 | +// label: string |
| 81 | +// href: string |
| 82 | +// parentId: string |
| 83 | +// children: GroupMenuData[string] |
| 84 | +// } |
| 85 | +
|
| 86 | +const menuData = ref(null) |
| 87 | +
|
| 88 | +onMounted(async () => { |
| 89 | + // The previous Bootstrap menu would download menu JSON |
| 90 | + // and then attach to specific elements in the DOM that |
| 91 | + // had this prefix in the 'class' attribute. |
| 92 | + const GROUP_MENU_PREFIX = 'group-parent-' |
| 93 | + // so we'll scrape those in order to build data for a |
| 94 | + // Reka menu |
| 95 | + const groupsLink = document.querySelector('[data-groups]') |
| 96 | + const groupsList = groupsLink.nextElementSibling |
| 97 | + const groupParents = groupsList.querySelectorAll(`li[class*=${GROUP_MENU_PREFIX}]`) |
| 98 | + const menu = Array.from(groupParents).map(element => { |
| 99 | + const parentId = element.getAttribute('class') |
| 100 | + .split(' ') |
| 101 | + .filter(classItem => classItem.includes(GROUP_MENU_PREFIX)) |
| 102 | + .reduce((_acc, classItem) => classItem.replace(GROUP_MENU_PREFIX, ''), null) |
| 103 | +
|
| 104 | + const href = element.querySelector('a[href]').getAttribute('href') |
| 105 | +
|
| 106 | + return { |
| 107 | + label: element.innerText, |
| 108 | + href, |
| 109 | + parentId, |
| 110 | + } |
| 111 | + }) |
| 112 | +
|
| 113 | + const updateMenu = (groupMenuData) => { |
| 114 | + menuData.value = menu.map(menuItem => ({ |
| 115 | + ...menuItem, |
| 116 | + children: groupBy(groupMenuData[menuItem.parentId], 'type') |
| 117 | + })) |
| 118 | + console.log({ menuDataValue: menuData.value }) |
| 119 | +
|
| 120 | + // remove original 'Groups' menu |
| 121 | + groupsLink.parentNode.removeChild(groupsLink) |
| 122 | + groupsList.parentNode.removeChild(groupsList) |
| 123 | + } |
| 124 | +
|
| 125 | + // Download JSON for menu |
| 126 | + const groupMenuDataUrl = document.body.dataset.groupMenuDataUrl |
| 127 | + const response = await fetch(groupMenuDataUrl) |
| 128 | + if (response.ok) { |
| 129 | + response.json().then((groupMenuData) => { |
| 130 | + updateMenu(groupMenuData) |
| 131 | + }) |
| 132 | + } else { |
| 133 | + console.error(`Problem downloading ${groupMenuDataUrl} ${response.status} ${response.statusText}`, response) |
| 134 | + } |
| 135 | +}) |
| 136 | +
|
| 137 | +</script> |
| 138 | + |
| 139 | +<style> |
| 140 | +.NavigationMenuRoot { |
| 141 | +} |
| 142 | +
|
| 143 | +.NavigationMenuList { |
| 144 | + list-style: none; |
| 145 | + margin: 0; |
| 146 | + padding: 0; |
| 147 | +} |
| 148 | +
|
| 149 | +.NavigationMenuTrigger { |
| 150 | + outline: none; |
| 151 | + user-select: none; |
| 152 | + line-height: 1; |
| 153 | + border: 0; |
| 154 | + background-color: inherit; |
| 155 | + color: var(--bs-nav-link-color); |
| 156 | + padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); |
| 157 | +} |
| 158 | +
|
| 159 | +.NavigationMenuLink { |
| 160 | + padding: 8px 12px; |
| 161 | + outline: none; |
| 162 | + user-select: none; |
| 163 | + font-weight: 500; |
| 164 | + line-height: 1; |
| 165 | + border: 0; |
| 166 | + display: block; |
| 167 | + text-decoration: none; |
| 168 | + line-height: 1; |
| 169 | +} |
| 170 | +
|
| 171 | +.NavigationMenuContent { |
| 172 | + position: absolute; |
| 173 | + animation-duration: 250ms; |
| 174 | + animation-timing-function: ease; |
| 175 | + background-color: #212529; |
| 176 | + border: solid 1px #ffffff26; |
| 177 | + border-radius: .375rem; |
| 178 | + padding: 0.5rem 0; |
| 179 | + min-width: 300px; |
| 180 | +} |
| 181 | +
|
| 182 | +.NavigationMenuContent[data-motion='from-start'] { |
| 183 | + animation-name: enterFromLeft; |
| 184 | +} |
| 185 | +.NavigationMenuContent[data-motion='from-end'] { |
| 186 | + animation-name: enterFromRight; |
| 187 | +} |
| 188 | +.NavigationMenuContent[data-motion='to-start'] { |
| 189 | + animation-name: exitToLeft; |
| 190 | +} |
| 191 | +.NavigationMenuContent[data-motion='to-end'] { |
| 192 | + animation-name: exitToRight; |
| 193 | +} |
| 194 | +
|
| 195 | +.NavigationMenu-NoList { |
| 196 | + list-style: none; |
| 197 | + margin: 0; |
| 198 | + padding: 0; |
| 199 | +} |
| 200 | +
|
| 201 | +.NavigationMenuLevel1 { |
| 202 | + border: 0; |
| 203 | + width: 100%; |
| 204 | + padding: 0.25rem 1rem; |
| 205 | + clear: both; |
| 206 | + color: #dee2e6; |
| 207 | + text-align:left; |
| 208 | + background: transparent; |
| 209 | +} |
| 210 | +
|
| 211 | +.NavigationMenuLevel1:hover, |
| 212 | +.NavigationMenuLevel1:focus { |
| 213 | + background: #2b3035; |
| 214 | +} |
| 215 | +
|
| 216 | +.NavigationMenuLevel2 { |
| 217 | + border: 0; |
| 218 | + width: 100%; |
| 219 | + padding: 0.25rem 1rem; |
| 220 | + margin-left: 1rem; |
| 221 | + clear: both; |
| 222 | + color: #dee2e6; |
| 223 | + text-align:left; |
| 224 | + background: transparent; |
| 225 | +} |
| 226 | +
|
| 227 | +.NavigationMenuLevel2:hover, |
| 228 | +.NavigationMenuLevel2:focus { |
| 229 | + background: #2b3035; |
| 230 | +} |
| 231 | +
|
| 232 | +
|
| 233 | +.NavigationMenuLegend { |
| 234 | + margin-left: 1rem; |
| 235 | + opacity: 0.8; |
| 236 | + font-size: inherit; |
| 237 | +} |
| 238 | +
|
| 239 | +.NavigationMenuViewport { |
| 240 | + position: fixed; |
| 241 | + transform-origin: top center; |
| 242 | + margin-top: 10px; |
| 243 | + width: 100%; |
| 244 | + background-color: white; |
| 245 | + border-radius: 6px; |
| 246 | + overflow: hidden; |
| 247 | + box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; |
| 248 | + height: var(--reka-navigation-menu-viewport-height); |
| 249 | + transition: width, height, 300ms ease; |
| 250 | +} |
| 251 | +.NavigationMenuViewport[data-state='open'] { |
| 252 | + animation: scaleIn 200ms ease; |
| 253 | +} |
| 254 | +.NavigationMenuViewport[data-state='closed'] { |
| 255 | + animation: scaleOut 200ms ease; |
| 256 | +} |
| 257 | +@media only screen and (min-width: 600px) { |
| 258 | + .NavigationMenuViewport { |
| 259 | + width: var(--reka-navigation-menu-viewport-width); |
| 260 | + } |
| 261 | +} |
| 262 | +
|
| 263 | +@keyframes enterFromRight { |
| 264 | + from { |
| 265 | + opacity: 0; |
| 266 | + transform: translateX(200px); |
| 267 | + } |
| 268 | + to { |
| 269 | + opacity: 1; |
| 270 | + transform: translateX(0); |
| 271 | + } |
| 272 | +} |
| 273 | +
|
| 274 | +@keyframes enterFromLeft { |
| 275 | + from { |
| 276 | + opacity: 0; |
| 277 | + transform: translateX(-200px); |
| 278 | + } |
| 279 | + to { |
| 280 | + opacity: 1; |
| 281 | + transform: translateX(0); |
| 282 | + } |
| 283 | +} |
| 284 | +
|
| 285 | +@keyframes exitToRight { |
| 286 | + from { |
| 287 | + opacity: 1; |
| 288 | + transform: translateX(0); |
| 289 | + } |
| 290 | + to { |
| 291 | + opacity: 0; |
| 292 | + transform: translateX(200px); |
| 293 | + } |
| 294 | +} |
| 295 | +
|
| 296 | +@keyframes exitToLeft { |
| 297 | + from { |
| 298 | + opacity: 1; |
| 299 | + transform: translateX(0); |
| 300 | + } |
| 301 | + to { |
| 302 | + opacity: 0; |
| 303 | + transform: translateX(-200px); |
| 304 | + } |
| 305 | +} |
| 306 | +
|
| 307 | +@keyframes scaleIn { |
| 308 | + from { |
| 309 | + opacity: 0; |
| 310 | + transform: rotateX(-30deg) scale(0.9); |
| 311 | + } |
| 312 | + to { |
| 313 | + opacity: 1; |
| 314 | + transform: rotateX(0deg) scale(1); |
| 315 | + } |
| 316 | +} |
| 317 | +
|
| 318 | +@keyframes scaleOut { |
| 319 | + from { |
| 320 | + opacity: 1; |
| 321 | + transform: rotateX(0deg) scale(1); |
| 322 | + } |
| 323 | + to { |
| 324 | + opacity: 0; |
| 325 | + transform: rotateX(-10deg) scale(0.95); |
| 326 | + } |
| 327 | +} |
| 328 | +
|
| 329 | +@keyframes fadeIn { |
| 330 | + from { |
| 331 | + opacity: 0; |
| 332 | + } |
| 333 | + to { |
| 334 | + opacity: 1; |
| 335 | + } |
| 336 | +} |
| 337 | +
|
| 338 | +@keyframes fadeOut { |
| 339 | + from { |
| 340 | + opacity: 1; |
| 341 | + } |
| 342 | + to { |
| 343 | + opacity: 0; |
| 344 | + } |
| 345 | +} |
| 346 | +</style> |
0 commit comments