@brillout/docpress 0.9.8 → 0.10.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.
- package/Layout.tsx +102 -40
- package/{navigation → MenuModal}/Collapsible.css +7 -0
- package/MenuModal/NavigationWithColumnLayout.css +11 -0
- package/MenuModal/NavigationWithColumnLayout.tsx +253 -0
- package/MenuModal/toggleMenuModal.ts +132 -0
- package/MenuModal.tsx +68 -79
- package/{navigation/Navigation.css → NavItemComponent.css} +1 -23
- package/NavItemComponent.tsx +149 -0
- package/components/Note.css +0 -1
- package/config/resolveHeadingsData.ts +1 -1
- package/css/code/diff.css +10 -5
- package/css/code.css +1 -1
- package/css/colorize-on-hover.css +6 -7
- package/css/heading.css +9 -3
- package/css/index.css +1 -0
- package/dist/NavItemComponent.d.ts +39 -0
- package/dist/NavItemComponent.js +109 -0
- package/dist/config/resolveHeadingsData.d.ts +1 -1
- package/dist/config/resolvePageContext.d.ts +2 -2
- package/dist/renderer/determineNavItemsColumnLayout.d.ts +1 -1
- package/docsearch/SearchLink.tsx +7 -3
- package/icons/books.svg +46 -0
- package/icons/gear.svg +35 -0
- package/icons/index.ts +5 -0
- package/icons/magnifying-glass.svg +31 -0
- package/icons/seedling.svg +24 -0
- package/index.ts +2 -0
- package/initKeyBindings.ts +1 -1
- package/package.json +1 -1
- package/renderer/determineNavItemsColumnLayout.ts +1 -1
- package/renderer/initOnNavigation.ts +1 -1
- package/renderer/onRenderClient.tsx +1 -1
- package/utils/css.ts +0 -6
- package/dist/Layout.d.ts +0 -15
- package/dist/Layout.js +0 -321
- package/dist/MenuModal.d.ts +0 -13
- package/dist/MenuModal.js +0 -124
- package/dist/NavSecondaryContent.d.ts +0 -6
- package/dist/NavSecondaryContent.js +0 -57
- package/dist/autoScrollNav.d.ts +0 -3
- package/dist/autoScrollNav.js +0 -35
- package/dist/components/EditPageNote.d.ts +0 -7
- package/dist/components/EditPageNote.js +0 -11
- package/dist/docsearch/SearchLink.d.ts +0 -4
- package/dist/docsearch/SearchLink.js +0 -25
- package/dist/docsearch/toggleDocsearchModal.d.ts +0 -4
- package/dist/docsearch/toggleDocsearchModal.js +0 -26
- package/dist/navigation/Collapsible.d.ts +0 -10
- package/dist/navigation/Collapsible.js +0 -35
- package/dist/navigation/Navigation.d.ts +0 -21
- package/dist/navigation/Navigation.js +0 -255
- package/dist/utils/PassTrough.d.ts +0 -3
- package/dist/utils/PassTrough.js +0 -6
- package/dist/utils/Style.d.ts +0 -5
- package/dist/utils/Style.js +0 -6
- package/dist/utils/css.d.ts +0 -1
- package/dist/utils/css.js +0 -27
- package/dist/utils/getViewportWidth.d.ts +0 -1
- package/dist/utils/getViewportWidth.js +0 -4
- package/dist/utils/throttle.d.ts +0 -1
- package/dist/utils/throttle.js +0 -14
- package/navigation/Navigation.tsx +0 -382
- /package/{navigation → MenuModal}/Collapsible.tsx +0 -0
package/Layout.tsx
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
export { Layout }
|
|
2
|
+
export { MenuToggle }
|
|
2
3
|
export { containerQueryMobileLayout }
|
|
3
4
|
export { containerQueryMobileMenu }
|
|
4
5
|
export { navLeftWidthMin }
|
|
5
6
|
export { navLeftWidthMax }
|
|
6
7
|
export { unexpandNav }
|
|
8
|
+
export { blockMargin }
|
|
7
9
|
|
|
8
10
|
import React from 'react'
|
|
9
|
-
import {
|
|
11
|
+
import { getNavItemsWithComputed, NavItem, NavItemComponent } from './NavItemComponent'
|
|
10
12
|
import { EditPageNote } from './components/EditPageNote'
|
|
11
13
|
import { parseTitle } from './parseTitle'
|
|
12
14
|
import { usePageContext, usePageContext2 } from './renderer/usePageContext'
|
|
13
15
|
import { NavSecondaryContent } from './NavSecondaryContent'
|
|
14
|
-
import {
|
|
16
|
+
import { closeMenuOnMouseLeave, openMenuModal, toggleMenuModal } from './MenuModal/toggleMenuModal'
|
|
15
17
|
import { MenuModal } from './MenuModal'
|
|
16
18
|
import { autoScrollNav_SSR } from './autoScrollNav'
|
|
17
19
|
import { SearchLink } from './docsearch/SearchLink'
|
|
@@ -20,6 +22,7 @@ import { css } from './utils/css'
|
|
|
20
22
|
import { PassThrough } from './utils/PassTrough'
|
|
21
23
|
import { Style } from './utils/Style'
|
|
22
24
|
import { cls } from './utils/cls'
|
|
25
|
+
import { iconBooks } from './icons'
|
|
23
26
|
|
|
24
27
|
const blockMargin = 3
|
|
25
28
|
const mainViewPadding = 20
|
|
@@ -242,6 +245,29 @@ function NavLeft() {
|
|
|
242
245
|
</>
|
|
243
246
|
)
|
|
244
247
|
}
|
|
248
|
+
function NavigationContent(props: {
|
|
249
|
+
navItems: NavItem[]
|
|
250
|
+
showOnlyRelevant?: true
|
|
251
|
+
}) {
|
|
252
|
+
const pageContext = usePageContext()
|
|
253
|
+
const navItemsWithComputed = getNavItemsWithComputed(props.navItems, pageContext.urlPathname)
|
|
254
|
+
|
|
255
|
+
let navItemsRelevant = navItemsWithComputed
|
|
256
|
+
if (props.showOnlyRelevant) navItemsRelevant = navItemsRelevant.filter((navItemGroup) => navItemGroup.isRelevant)
|
|
257
|
+
const navContent = navItemsRelevant.map((navItem, i) => <NavItemComponent navItem={navItem} key={i} />)
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div className="navigation-content" style={{ marginTop: 10 }}>
|
|
261
|
+
{navContent}
|
|
262
|
+
</div>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const menuLinkStyle: React.CSSProperties = {
|
|
267
|
+
height: '100%',
|
|
268
|
+
padding: '0 var(--padding-side)',
|
|
269
|
+
justifyContent: 'center',
|
|
270
|
+
}
|
|
245
271
|
|
|
246
272
|
function NavHead({ isNavLeft }: { isNavLeft?: true }) {
|
|
247
273
|
const pageContext = usePageContext()
|
|
@@ -250,12 +276,6 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
|
|
|
250
276
|
const { isLandingPage } = pageContext
|
|
251
277
|
const { navMaxWidth } = pageContext.config
|
|
252
278
|
|
|
253
|
-
const linkStyle: React.CSSProperties = {
|
|
254
|
-
height: '100%',
|
|
255
|
-
padding: '0 var(--padding-side)',
|
|
256
|
-
justifyContent: 'center',
|
|
257
|
-
}
|
|
258
|
-
|
|
259
279
|
const TopNavigation = pageContext2.config.TopNavigation || PassThrough
|
|
260
280
|
const navSecondaryContent = (
|
|
261
281
|
<div
|
|
@@ -270,7 +290,6 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
|
|
|
270
290
|
position: 'absolute',
|
|
271
291
|
left: '100%',
|
|
272
292
|
top: 0,
|
|
273
|
-
paddingLeft: 'var(--block-margin)',
|
|
274
293
|
'--padding-side': '20px',
|
|
275
294
|
width: mainViewMax, // guaranteed real estate
|
|
276
295
|
}),
|
|
@@ -325,8 +344,8 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
|
|
|
325
344
|
>
|
|
326
345
|
<NavLogo className="mobile-grow-half" />
|
|
327
346
|
{!isNavLeft && <div className="desktop-grow" />}
|
|
328
|
-
<SearchLink className="mobile-grow-half" style={
|
|
329
|
-
<
|
|
347
|
+
<SearchLink className="mobile-grow-half" style={menuLinkStyle} />
|
|
348
|
+
<MenuToggleMain className="mobile-grow-full" style={menuLinkStyle} />
|
|
330
349
|
{navSecondaryContent}
|
|
331
350
|
</div>
|
|
332
351
|
</div>
|
|
@@ -353,12 +372,17 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
|
|
|
353
372
|
}
|
|
354
373
|
@container container-nav-head (min-width: 501px) {
|
|
355
374
|
.nav-head-content {
|
|
356
|
-
--padding-side:
|
|
375
|
+
--padding-side: 24px;
|
|
357
376
|
}
|
|
358
377
|
.nav-logo {
|
|
359
378
|
padding: 0 var(--padding-side);
|
|
360
379
|
}
|
|
361
380
|
}
|
|
381
|
+
@container container-nav-head (min-width: ${containerQueryMobileMenu + 100}px) {
|
|
382
|
+
.nav-head-content {
|
|
383
|
+
--padding-side: 35px;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
362
386
|
@media(max-width: ${containerQueryMobileMenu}px) {
|
|
363
387
|
.hide-on-shrink {
|
|
364
388
|
display: none !important;
|
|
@@ -372,7 +396,7 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
|
|
|
372
396
|
flex-grow: 1;
|
|
373
397
|
}
|
|
374
398
|
.desktop-fade {
|
|
375
|
-
transition: opacity 0.
|
|
399
|
+
transition: opacity 0.4s ease-in-out;
|
|
376
400
|
}
|
|
377
401
|
html:not(.menu-modal-show) .nav-head-top:not(:hover) .desktop-fade {
|
|
378
402
|
transition: opacity 0.3s ease-in-out !important;
|
|
@@ -491,7 +515,31 @@ function isProjectNameShort(projectName: string) {
|
|
|
491
515
|
|
|
492
516
|
let onMouseIgnore: ReturnType<typeof setTimeout> | undefined
|
|
493
517
|
type PropsDiv = React.HTMLProps<HTMLDivElement>
|
|
494
|
-
function
|
|
518
|
+
function MenuToggleMain(props: PropsDiv) {
|
|
519
|
+
return (
|
|
520
|
+
<MenuToggle menuId={0} {...props}>
|
|
521
|
+
<span className="text-docs" style={{ display: 'flex' }}>
|
|
522
|
+
<DocsIcon /> Docs
|
|
523
|
+
</span>
|
|
524
|
+
<span className="text-menu">
|
|
525
|
+
<MenuIcon /> Menu
|
|
526
|
+
</span>
|
|
527
|
+
<Style>{css`
|
|
528
|
+
@media(max-width: ${containerQueryMobileMenu}px) {
|
|
529
|
+
.text-docs {
|
|
530
|
+
display: none !important;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
@media(min-width: ${containerQueryMobileMenu + 1}px) {
|
|
534
|
+
.text-menu {
|
|
535
|
+
display: none;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
`}</Style>
|
|
539
|
+
</MenuToggle>
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
function MenuToggle({ menuId, ...props }: PropsDiv & { menuId: number }) {
|
|
495
543
|
return (
|
|
496
544
|
<div
|
|
497
545
|
{...props}
|
|
@@ -499,22 +547,23 @@ function MenuLink(props: PropsDiv) {
|
|
|
499
547
|
height: '100%',
|
|
500
548
|
display: 'flex',
|
|
501
549
|
alignItems: 'center',
|
|
502
|
-
cursor: '
|
|
550
|
+
cursor: 'pointer',
|
|
503
551
|
userSelect: 'none',
|
|
552
|
+
...menuLinkStyle,
|
|
504
553
|
...props.style,
|
|
505
554
|
}}
|
|
506
|
-
className={[
|
|
555
|
+
className={[`colorize-on-hover menu-toggle menu-toggle-${menuId}`, props.className].filter(Boolean).join(' ')}
|
|
507
556
|
onClick={(ev) => {
|
|
508
557
|
ev.preventDefault()
|
|
509
|
-
toggleMenuModal()
|
|
558
|
+
toggleMenuModal(menuId)
|
|
510
559
|
}}
|
|
511
|
-
|
|
560
|
+
onMouseEnter={() => {
|
|
512
561
|
if (onMouseIgnore) return
|
|
513
|
-
openMenuModal()
|
|
562
|
+
openMenuModal(menuId)
|
|
514
563
|
}}
|
|
515
564
|
onMouseLeave={() => {
|
|
516
565
|
if (onMouseIgnore) return
|
|
517
|
-
|
|
566
|
+
closeMenuOnMouseLeave()
|
|
518
567
|
}}
|
|
519
568
|
onTouchStart={() => {
|
|
520
569
|
onMouseIgnore = setTimeout(() => {
|
|
@@ -522,32 +571,45 @@ function MenuLink(props: PropsDiv) {
|
|
|
522
571
|
}, 1000)
|
|
523
572
|
}}
|
|
524
573
|
>
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
574
|
+
<Style>{getAnimation()}</Style>
|
|
575
|
+
{props.children}
|
|
576
|
+
</div>
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
function getAnimation() {
|
|
580
|
+
return css`
|
|
581
|
+
.menu-toggle {
|
|
582
|
+
position: relative;
|
|
583
|
+
overflow: hidden;
|
|
584
|
+
z-index: 0;
|
|
585
|
+
@media (hover: hover) and (pointer: fine) {
|
|
586
|
+
.link-hover-animation &:hover::before {
|
|
587
|
+
top: 0;
|
|
588
|
+
}
|
|
535
589
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
590
|
+
&::before {
|
|
591
|
+
position: absolute;
|
|
592
|
+
content: '';
|
|
593
|
+
height: 100%;
|
|
594
|
+
width: 100%;
|
|
595
|
+
top: var(--nav-head-height);
|
|
596
|
+
background-color: var(--active-color);
|
|
597
|
+
transition-property: top !important;
|
|
598
|
+
transition: top 0.4s ease !important;
|
|
599
|
+
z-index: -1;
|
|
540
600
|
}
|
|
541
601
|
}
|
|
542
|
-
`
|
|
543
|
-
|
|
544
|
-
)
|
|
602
|
+
`
|
|
603
|
+
}
|
|
545
604
|
}
|
|
546
605
|
function DocsIcon() {
|
|
547
606
|
return (
|
|
548
|
-
<
|
|
549
|
-
|
|
550
|
-
|
|
607
|
+
<img
|
|
608
|
+
src={iconBooks}
|
|
609
|
+
width={21}
|
|
610
|
+
style={{ marginRight: 'calc(var(--icon-text-padding) + 2px)' }}
|
|
611
|
+
className="decolorize-5 desktop-fade"
|
|
612
|
+
/>
|
|
551
613
|
)
|
|
552
614
|
}
|
|
553
615
|
function MenuIcon() {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
.collapsible-icon {
|
|
2
2
|
display: none;
|
|
3
|
+
position: absolute;
|
|
4
|
+
top: calc(100% / 2 - 6px);
|
|
5
|
+
margin-left: 9px;
|
|
6
|
+
vertical-align: middle;
|
|
7
|
+
}
|
|
8
|
+
:has(> .collapsible-icon) {
|
|
9
|
+
position: relative;
|
|
3
10
|
}
|
|
4
11
|
.collapsible-expanded .collapsible-icon,
|
|
5
12
|
.collapsible-collapsed .collapsible-icon {
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
export { NavigationWithColumnLayout }
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react'
|
|
4
|
+
import { assert } from '../utils/server'
|
|
5
|
+
import { getViewportWidth } from '../utils/getViewportWidth'
|
|
6
|
+
import { containerQueryMobileMenu, navLeftWidthMax, navLeftWidthMin } from '../Layout'
|
|
7
|
+
import { throttle } from '../utils/throttle'
|
|
8
|
+
import { Collapsible } from './Collapsible'
|
|
9
|
+
import { ColumnMap, getNavItemsWithComputed, NavItem, NavItemComponent, NavItemComputed } from '../NavItemComponent'
|
|
10
|
+
import { usePageContext } from '../renderer/usePageContext'
|
|
11
|
+
import './NavigationWithColumnLayout.css'
|
|
12
|
+
import { Style } from '../utils/Style'
|
|
13
|
+
import { css } from '../utils/css'
|
|
14
|
+
|
|
15
|
+
const marginBottomOnExpand = 30
|
|
16
|
+
function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
|
|
17
|
+
const pageContext = usePageContext()
|
|
18
|
+
const navItemsWithComputed = getNavItemsWithComputed(props.navItems, pageContext.urlPathname)
|
|
19
|
+
let [viewportWidth, setViewportWidth] = useState<number | undefined>()
|
|
20
|
+
const updateviewportwidth = () => setViewportWidth(getViewportWidth())
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
updateviewportwidth()
|
|
23
|
+
window.addEventListener('resize', throttle(updateviewportwidth, 300), { passive: true })
|
|
24
|
+
})
|
|
25
|
+
const navItemsByColumnLayouts = getNavItemsByColumnLayouts(navItemsWithComputed, viewportWidth)
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<Style>{getStyle()}</Style>
|
|
29
|
+
<div className="navigation-content" style={{ paddingTop: 10 }}>
|
|
30
|
+
{navItemsByColumnLayouts.map((columnLayout, i) => (
|
|
31
|
+
<div id={`menu-navigation-${i}`} className="menu-navigation" key={i}>
|
|
32
|
+
{columnLayout.isFullWidthCategory ? (
|
|
33
|
+
<div style={{ marginTop: 0 }}>
|
|
34
|
+
<ColumnsWrapper numberOfColumns={columnLayout.columns.length}>
|
|
35
|
+
<Collapsible
|
|
36
|
+
head={(onClick) => <NavItemComponent navItem={columnLayout.navItemLevel1} onClick={onClick} />}
|
|
37
|
+
disabled={columnLayout.columns.length > 1}
|
|
38
|
+
collapsedInit={!columnLayout.navItemLevel1.isRelevant}
|
|
39
|
+
marginBottomOnExpand={marginBottomOnExpand}
|
|
40
|
+
>
|
|
41
|
+
<ColumnsLayout className="collapsible">
|
|
42
|
+
{columnLayout.columns.map((column, j) => (
|
|
43
|
+
<Column key={j}>
|
|
44
|
+
{column.navItems.map((navItem, k) => (
|
|
45
|
+
<NavItemComponent key={k} navItem={navItem} />
|
|
46
|
+
))}
|
|
47
|
+
</Column>
|
|
48
|
+
))}
|
|
49
|
+
<CategoryBorder navItemLevel1={columnLayout.navItemLevel1} />
|
|
50
|
+
</ColumnsLayout>
|
|
51
|
+
</Collapsible>
|
|
52
|
+
</ColumnsWrapper>
|
|
53
|
+
</div>
|
|
54
|
+
) : (
|
|
55
|
+
<ColumnsWrapper numberOfColumns={columnLayout.columns.length}>
|
|
56
|
+
<ColumnsLayout>
|
|
57
|
+
{columnLayout.columns.map((column, j) => (
|
|
58
|
+
<Column key={j}>
|
|
59
|
+
{column.categories.map((category, k) => (
|
|
60
|
+
<div key={k} style={{ marginBottom: 0 }}>
|
|
61
|
+
<Collapsible
|
|
62
|
+
head={(onClick) => <NavItemComponent navItem={category.navItemLevel1} onClick={onClick} />}
|
|
63
|
+
disabled={columnLayout.columns.length > 1}
|
|
64
|
+
collapsedInit={!category.navItemLevel1.isRelevant}
|
|
65
|
+
marginBottomOnExpand={marginBottomOnExpand}
|
|
66
|
+
>
|
|
67
|
+
{category.navItems.map((navItem, l) => (
|
|
68
|
+
<NavItemComponent key={l} navItem={navItem} />
|
|
69
|
+
))}
|
|
70
|
+
<CategoryBorder navItemLevel1={category.navItemLevel1} />
|
|
71
|
+
</Collapsible>
|
|
72
|
+
</div>
|
|
73
|
+
))}
|
|
74
|
+
</Column>
|
|
75
|
+
))}
|
|
76
|
+
</ColumnsLayout>
|
|
77
|
+
</ColumnsWrapper>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
</>
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
function getStyle() {
|
|
86
|
+
const style = css`
|
|
87
|
+
@media(min-width: ${containerQueryMobileMenu + 1}px) {
|
|
88
|
+
${navItemsByColumnLayouts
|
|
89
|
+
.map(
|
|
90
|
+
(_, i) =>
|
|
91
|
+
css`
|
|
92
|
+
html:not(.menu-modal-show-${i}) #menu-navigation-${i} {
|
|
93
|
+
display: none;
|
|
94
|
+
}
|
|
95
|
+
html.menu-modal-show.menu-modal-show-${i} {
|
|
96
|
+
.menu-toggle-${i} {
|
|
97
|
+
color: black !important;
|
|
98
|
+
cursor: default !important;
|
|
99
|
+
[class^='decolorize-'],
|
|
100
|
+
[class*=' decolorize-'] {
|
|
101
|
+
filter: grayscale(0) opacity(1) !important;
|
|
102
|
+
}
|
|
103
|
+
&::before {
|
|
104
|
+
top: 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
`,
|
|
109
|
+
)
|
|
110
|
+
.join('')}
|
|
111
|
+
}
|
|
112
|
+
`
|
|
113
|
+
return style
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function Column({ children }: { children: React.ReactNode }) {
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
style={{
|
|
120
|
+
flexGrow: 1,
|
|
121
|
+
maxWidth: navLeftWidthMax,
|
|
122
|
+
display: 'flex',
|
|
123
|
+
flexDirection: 'column',
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
function ColumnsWrapper({ children, numberOfColumns }: { children: React.ReactNode; numberOfColumns: number }) {
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
style={{
|
|
134
|
+
width: numberOfColumns * (navLeftWidthMax + 20),
|
|
135
|
+
maxWidth: '100%',
|
|
136
|
+
margin: 'auto',
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{children}
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
function ColumnsLayout({ children, className }: { children: React.ReactNode; className?: string }) {
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
className={className}
|
|
147
|
+
style={{
|
|
148
|
+
display: 'flex',
|
|
149
|
+
justifyContent: 'space-between',
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
{children}
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
function CategoryBorder({ navItemLevel1 }: { navItemLevel1: NavItemComputed }) {
|
|
157
|
+
assert(navItemLevel1.level === 1)
|
|
158
|
+
return <div className="category-border" style={{ background: navItemLevel1.color! }} />
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
type NavItemsByColumnLayout =
|
|
162
|
+
| {
|
|
163
|
+
columns: {
|
|
164
|
+
categories: {
|
|
165
|
+
navItemLevel1: NavItemComputed
|
|
166
|
+
navItems: NavItemComputed[]
|
|
167
|
+
}[]
|
|
168
|
+
}[]
|
|
169
|
+
isFullWidthCategory: false
|
|
170
|
+
}
|
|
171
|
+
| {
|
|
172
|
+
navItemLevel1: NavItemComputed
|
|
173
|
+
columns: { navItems: NavItemComputed[] }[]
|
|
174
|
+
isFullWidthCategory: true
|
|
175
|
+
}
|
|
176
|
+
function getNavItemsByColumnLayouts(navItems: NavItemComputed[], viewportWidth: number = 0): NavItemsByColumnLayout[] {
|
|
177
|
+
const navItemsByColumnEntries = getNavItemsByColumnEntries(navItems)
|
|
178
|
+
const numberOfColumnsMax = Math.floor(viewportWidth / navLeftWidthMin) || 1
|
|
179
|
+
const navItemsByColumnLayouts: NavItemsByColumnLayout[] = navItemsByColumnEntries.map(
|
|
180
|
+
({ columnEntries, isFullWidthCategory }) => {
|
|
181
|
+
const numberOfColumns = Math.min(numberOfColumnsMax, columnEntries.length)
|
|
182
|
+
if (!isFullWidthCategory) {
|
|
183
|
+
const columns: {
|
|
184
|
+
categories: {
|
|
185
|
+
navItemLevel1: NavItemComputed
|
|
186
|
+
navItems: NavItemComputed[]
|
|
187
|
+
}[]
|
|
188
|
+
}[] = []
|
|
189
|
+
columnEntries.forEach((columnEntry) => {
|
|
190
|
+
const idx = numberOfColumns === 1 ? 0 : columnEntry.columnMap[numberOfColumns]!
|
|
191
|
+
assert(idx >= 0)
|
|
192
|
+
columns[idx] ??= { categories: [] }
|
|
193
|
+
const navItemLevel1 = columnEntry.navItems[0]
|
|
194
|
+
const navItems = columnEntry.navItems.slice(1)
|
|
195
|
+
columns[idx].categories.push({ navItemLevel1, navItems })
|
|
196
|
+
})
|
|
197
|
+
const navItemsByColumnLayout: NavItemsByColumnLayout = { columns, isFullWidthCategory }
|
|
198
|
+
return navItemsByColumnLayout
|
|
199
|
+
} else {
|
|
200
|
+
let navItemLevel1: NavItemComputed
|
|
201
|
+
const columns: { navItems: NavItemComputed[] }[] = []
|
|
202
|
+
columnEntries.forEach((columnEntry, i) => {
|
|
203
|
+
const idx = numberOfColumns === 1 ? 0 : columnEntry.columnMap[numberOfColumns]!
|
|
204
|
+
assert(idx >= 0)
|
|
205
|
+
columns[idx] ??= { navItems: [] }
|
|
206
|
+
let { navItems } = columnEntry
|
|
207
|
+
if (i === 0) {
|
|
208
|
+
navItemLevel1 = navItems[0]
|
|
209
|
+
navItems = navItems.slice(1)
|
|
210
|
+
}
|
|
211
|
+
columns[idx].navItems.push(...navItems)
|
|
212
|
+
})
|
|
213
|
+
const navItemsByColumnLayout: NavItemsByColumnLayout = {
|
|
214
|
+
columns,
|
|
215
|
+
navItemLevel1: navItemLevel1!,
|
|
216
|
+
isFullWidthCategory,
|
|
217
|
+
}
|
|
218
|
+
return navItemsByColumnLayout
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
return navItemsByColumnLayouts
|
|
223
|
+
}
|
|
224
|
+
type NavItemsByColumnEntries = { columnEntries: ColumnEntry[]; isFullWidthCategory: boolean }[]
|
|
225
|
+
type ColumnEntry = { navItems: NavItemComputed[]; columnMap: ColumnMap }
|
|
226
|
+
function getNavItemsByColumnEntries(navItems: NavItemComputed[]): NavItemsByColumnEntries {
|
|
227
|
+
const navItemsByColumnEntries: NavItemsByColumnEntries = []
|
|
228
|
+
let columnEntries: ColumnEntry[] = []
|
|
229
|
+
let columnEntry: ColumnEntry
|
|
230
|
+
let isFullWidthCategory: boolean | undefined
|
|
231
|
+
navItems.forEach((navItem) => {
|
|
232
|
+
if (navItem.level === 1) {
|
|
233
|
+
const isFullWidthCategoryPrevious = isFullWidthCategory
|
|
234
|
+
isFullWidthCategory = !!navItem.menuModalFullWidth
|
|
235
|
+
if (isFullWidthCategoryPrevious !== undefined && isFullWidthCategoryPrevious !== isFullWidthCategory) {
|
|
236
|
+
navItemsByColumnEntries.push({ columnEntries, isFullWidthCategory: isFullWidthCategoryPrevious })
|
|
237
|
+
columnEntries = []
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
assert(isFullWidthCategory !== undefined)
|
|
241
|
+
if (navItem.isColumnEntry) {
|
|
242
|
+
assert(navItem.level === 1 || navItem.level === 4)
|
|
243
|
+
columnEntry = { navItems: [navItem], columnMap: navItem.isColumnEntry }
|
|
244
|
+
columnEntries.push(columnEntry)
|
|
245
|
+
} else {
|
|
246
|
+
assert(navItem.level !== 1)
|
|
247
|
+
columnEntry.navItems.push(navItem)
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
assert(isFullWidthCategory !== undefined)
|
|
251
|
+
navItemsByColumnEntries.push({ columnEntries, isFullWidthCategory })
|
|
252
|
+
return navItemsByColumnEntries
|
|
253
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
export { toggleMenuModal }
|
|
2
|
+
export { openMenuModal }
|
|
3
|
+
export { keepMenuModalOpen }
|
|
4
|
+
export { closeMenuModal }
|
|
5
|
+
export { closeMenuOnMouseLeave }
|
|
6
|
+
export { addListenerOpenMenuModal }
|
|
7
|
+
|
|
8
|
+
import { containerQueryMobileLayout } from '../Layout'
|
|
9
|
+
import { getViewportWidth } from '../utils/getViewportWidth'
|
|
10
|
+
import { isBrowser } from '../utils/isBrowser'
|
|
11
|
+
|
|
12
|
+
initScrollListener()
|
|
13
|
+
|
|
14
|
+
function keepMenuModalOpen() {
|
|
15
|
+
if (keepOpenIsDisabled) return
|
|
16
|
+
open()
|
|
17
|
+
}
|
|
18
|
+
function openMenuModal(menuNavigationId: number) {
|
|
19
|
+
open(menuNavigationId)
|
|
20
|
+
}
|
|
21
|
+
function open(menuNavigationId?: number) {
|
|
22
|
+
if (menuModalLock) {
|
|
23
|
+
if (menuNavigationId === undefined) {
|
|
24
|
+
clearTimeout(menuModalLock?.timeout)
|
|
25
|
+
menuModalLock = undefined
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
menuModalLock.idNext = menuNavigationId
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
const { classList } = document.documentElement
|
|
32
|
+
classList.add('menu-modal-show')
|
|
33
|
+
if (menuNavigationId !== undefined) {
|
|
34
|
+
const currentModalId = getCurrentMenuId()
|
|
35
|
+
if (currentModalId === menuNavigationId) return
|
|
36
|
+
if (currentModalId !== null) {
|
|
37
|
+
classList.remove(`menu-modal-show-${currentModalId}`)
|
|
38
|
+
}
|
|
39
|
+
classList.add(`menu-modal-show-${menuNavigationId}`)
|
|
40
|
+
}
|
|
41
|
+
listener?.()
|
|
42
|
+
}
|
|
43
|
+
let listener: () => void | undefined
|
|
44
|
+
function addListenerOpenMenuModal(cb: () => void) {
|
|
45
|
+
listener = cb
|
|
46
|
+
}
|
|
47
|
+
function closeMenuModal() {
|
|
48
|
+
document.documentElement.classList.remove('menu-modal-show')
|
|
49
|
+
}
|
|
50
|
+
let keepOpenIsDisabled: true | undefined
|
|
51
|
+
function closeAndForbidKeepOpen() {
|
|
52
|
+
if (!document.documentElement.classList.contains('menu-modal-show')) return
|
|
53
|
+
keepOpenIsDisabled = true
|
|
54
|
+
closeMenuModal()
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
keepOpenIsDisabled = undefined
|
|
57
|
+
}, 500)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let menuModalLock:
|
|
61
|
+
| {
|
|
62
|
+
idCurrent: number
|
|
63
|
+
idNext: number | undefined
|
|
64
|
+
timeout: NodeJS.Timeout
|
|
65
|
+
}
|
|
66
|
+
| undefined
|
|
67
|
+
function closeMenuOnMouseLeave() {
|
|
68
|
+
const currentModalId = getCurrentMenuId()
|
|
69
|
+
if (currentModalId === null) return
|
|
70
|
+
const timeout = setTimeout(() => {
|
|
71
|
+
const { idCurrent, idNext } = menuModalLock!
|
|
72
|
+
menuModalLock = undefined
|
|
73
|
+
if (idNext === idCurrent) return
|
|
74
|
+
if (idNext === undefined) {
|
|
75
|
+
closeAndForbidKeepOpen()
|
|
76
|
+
} else {
|
|
77
|
+
openMenuModal(idNext)
|
|
78
|
+
}
|
|
79
|
+
}, 100)
|
|
80
|
+
clearTimeout(menuModalLock?.timeout)
|
|
81
|
+
menuModalLock = {
|
|
82
|
+
idCurrent: currentModalId,
|
|
83
|
+
idNext: undefined,
|
|
84
|
+
timeout,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function getCurrentMenuId(): null | number {
|
|
88
|
+
const { classList } = document.documentElement
|
|
89
|
+
const prefix = 'menu-modal-show-'
|
|
90
|
+
const cls = Array.from(classList).find((cls) => cls.startsWith(prefix))
|
|
91
|
+
if (!cls) return null
|
|
92
|
+
return parseInt(cls.slice(prefix.length), 10)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function initScrollListener() {
|
|
96
|
+
if (!isBrowser()) return
|
|
97
|
+
window.addEventListener('scroll', closeAndForbidKeepOpen, { passive: true })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toggleMenuModal(menuId: number) {
|
|
101
|
+
keepOpenIsDisabled = undefined
|
|
102
|
+
const { classList } = document.documentElement
|
|
103
|
+
if (classList.contains('menu-modal-show') && classList.contains(`menu-modal-show-${menuId}`)) {
|
|
104
|
+
closeMenuModal()
|
|
105
|
+
} else {
|
|
106
|
+
openMenuModal(menuId)
|
|
107
|
+
if (getViewportWidth() < containerQueryMobileLayout) autoScroll()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function autoScroll() {
|
|
112
|
+
const nav = document.querySelector('#menu-modal .navigation-content')!
|
|
113
|
+
const href = window.location.pathname
|
|
114
|
+
const navLinks = Array.from(nav.querySelectorAll(`a[href="${href}"]`))
|
|
115
|
+
const navLink = navLinks[0] as HTMLElement | undefined
|
|
116
|
+
if (!navLink) return
|
|
117
|
+
// None of the following seemes to be working: https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
|
|
118
|
+
if (findCollapsibleEl(navLink)!.classList.contains('collapsible-collapsed')) return
|
|
119
|
+
navLink.scrollIntoView({
|
|
120
|
+
behavior: 'instant',
|
|
121
|
+
block: 'center',
|
|
122
|
+
inline: 'start',
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
function findCollapsibleEl(navLink: HTMLElement | undefined) {
|
|
126
|
+
let parentEl: HTMLElement | null | undefined = navLink
|
|
127
|
+
while (parentEl) {
|
|
128
|
+
if (parentEl.classList.contains('collapsible')) return parentEl
|
|
129
|
+
parentEl = parentEl.parentElement
|
|
130
|
+
}
|
|
131
|
+
return null
|
|
132
|
+
}
|