@brillout/docpress 0.15.13 → 0.16.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.
@@ -3,7 +3,7 @@ export { NavigationWithColumnLayout }
3
3
  import React, { useEffect, useState } from 'react'
4
4
  import { assert } from '../utils/server'
5
5
  import { getViewportWidth } from '../utils/getViewportWidth'
6
- import { containerQueryMobileMenu, navLeftWidthMax, navLeftWidthMin } from '../Layout'
6
+ import { viewTablet, navLeftWidthMax, navLeftWidthMin, bodyMaxWidth } from '../Layout'
7
7
  import { throttle } from '../utils/throttle'
8
8
  import { Collapsible } from './Collapsible'
9
9
  import { ColumnMap, getNavItemsWithComputed, NavItem, NavItemComponent, NavItemComputed } from '../NavItemComponent'
@@ -22,7 +22,18 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
22
22
  updateviewportwidth()
23
23
  window.addEventListener('resize', throttle(updateviewportwidth, 300), { passive: true })
24
24
  })
25
- const navItemsByColumnLayouts = getNavItemsByColumnLayouts(navItemsWithComputed, viewportWidth)
25
+ const availableWidth = viewportWidth && Math.min(viewportWidth, bodyMaxWidth)
26
+ const navItemsByColumnLayouts = getNavItemsByColumnLayouts(navItemsWithComputed, availableWidth)
27
+ const columnWidthBase = navLeftWidthMax + 20
28
+ const maxColumns = Math.max(...navItemsByColumnLayouts.map((layout) => layout.columns.length), 1)
29
+ const widthMax = maxColumns * columnWidthBase
30
+ const getColumnsWrapperStyle = (columnLayout: NavItemsByColumnLayout) => {
31
+ const widthColumn = columnLayout.columns.length * columnWidthBase
32
+ return {
33
+ width: Math.max(700, widthColumn),
34
+ maxWidth: `min(100%, ${widthMax}px)`,
35
+ }
36
+ }
26
37
  return (
27
38
  <>
28
39
  <Style>{getStyle()}</Style>
@@ -40,10 +51,10 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
40
51
  >
41
52
  {columnLayout.isFullWidthCategory ? (
42
53
  <div style={{ marginTop: 0 }}>
43
- <ColumnsWrapper numberOfColumns={columnLayout.columns.length}>
54
+ <ColumnsWrapper style={getColumnsWrapperStyle(columnLayout)}>
44
55
  <Collapsible
45
56
  head={(onClick) => <NavItemComponent navItem={columnLayout.navItemLevel1} onClick={onClick} />}
46
- disabled={columnLayout.columns.length > 1}
57
+ disabled={maxColumns > 1}
47
58
  collapsedInit={!columnLayout.navItemLevel1.isRelevant}
48
59
  marginBottomOnExpand={marginBottomOnExpand}
49
60
  >
@@ -61,7 +72,7 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
61
72
  </ColumnsWrapper>
62
73
  </div>
63
74
  ) : (
64
- <ColumnsWrapper numberOfColumns={columnLayout.columns.length}>
75
+ <ColumnsWrapper style={getColumnsWrapperStyle(columnLayout)}>
65
76
  <ColumnsLayout>
66
77
  {columnLayout.columns.map((column, j) => (
67
78
  <Column key={j}>
@@ -69,7 +80,7 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
69
80
  <div key={k} style={{ marginBottom: 0 }}>
70
81
  <Collapsible
71
82
  head={(onClick) => <NavItemComponent navItem={category.navItemLevel1} onClick={onClick} />}
72
- disabled={columnLayout.columns.length > 1}
83
+ disabled={maxColumns > 1}
73
84
  collapsedInit={!category.navItemLevel1.isRelevant}
74
85
  marginBottomOnExpand={marginBottomOnExpand}
75
86
  >
@@ -93,7 +104,7 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
93
104
 
94
105
  function getStyle() {
95
106
  const style = css`
96
- @media(min-width: ${containerQueryMobileMenu + 1}px) {
107
+ @media(min-width: ${viewTablet + 1}px) {
97
108
  .menu-navigation-content {
98
109
  position: absolute;
99
110
  width: 100%;
@@ -165,14 +176,14 @@ function Column({ children }: { children: React.ReactNode }) {
165
176
  </div>
166
177
  )
167
178
  }
168
- function ColumnsWrapper({ children, numberOfColumns }: { children: React.ReactNode; numberOfColumns: number }) {
179
+ function ColumnsWrapper({ children, style }: { children: React.ReactNode; style: React.CSSProperties }) {
169
180
  return (
170
181
  <div
182
+ className="columns-wrapper"
171
183
  style={{
172
- width: numberOfColumns * (navLeftWidthMax + 20),
173
- maxWidth: '100%',
174
184
  paddingLeft: 3,
175
185
  margin: 'auto',
186
+ ...style,
176
187
  }}
177
188
  >
178
189
  {children}
@@ -212,9 +223,9 @@ type NavItemsByColumnLayout =
212
223
  columns: { navItems: NavItemComputed[] }[]
213
224
  isFullWidthCategory: true
214
225
  }
215
- function getNavItemsByColumnLayouts(navItems: NavItemComputed[], viewportWidth: number = 0): NavItemsByColumnLayout[] {
226
+ function getNavItemsByColumnLayouts(navItems: NavItemComputed[], availableWidth: number = 0): NavItemsByColumnLayout[] {
216
227
  const navItemsByColumnEntries = getNavItemsByColumnEntries(navItems)
217
- const numberOfColumnsMax = Math.floor(viewportWidth / navLeftWidthMin) || 1
228
+ const numberOfColumnsMax = Math.floor(availableWidth / navLeftWidthMin) || 1
218
229
  const navItemsByColumnLayouts: NavItemsByColumnLayout[] = navItemsByColumnEntries.map(
219
230
  ({ columnEntries, isFullWidthCategory }) => {
220
231
  const numberOfColumns = Math.min(numberOfColumnsMax, columnEntries.length)
@@ -269,17 +280,23 @@ function getNavItemsByColumnEntries(navItems: NavItemComputed[]): NavItemsByColu
269
280
  let isFullWidthCategory: boolean | undefined
270
281
  navItems.forEach((navItem) => {
271
282
  if (navItem.level === 1) {
272
- const isFullWidthCategoryPrevious = isFullWidthCategory
273
- isFullWidthCategory = !!navItem.menuModalFullWidth
274
- if (isFullWidthCategoryPrevious !== undefined && isFullWidthCategoryPrevious !== isFullWidthCategory) {
283
+ if (isFullWidthCategory) {
284
+ assert(navItem.menuModalFullWidth)
285
+ }
286
+ const isFullWidthCategoryPrevious = !!isFullWidthCategory
287
+ if (navItem.menuModalFullWidth) {
288
+ isFullWidthCategory = true
289
+ // Flush
275
290
  navItemsByColumnEntries.push({ columnEntries, isFullWidthCategory: isFullWidthCategoryPrevious })
276
291
  columnEntries = []
292
+ } else {
293
+ isFullWidthCategory = false
277
294
  }
278
295
  }
279
296
  assert(isFullWidthCategory !== undefined)
280
- if (navItem.isColumnEntry) {
297
+ if (navItem.isPotentialColumn) {
281
298
  assert(navItem.level === 1 || navItem.level === 4)
282
- columnEntry = { navItems: [navItem], columnMap: navItem.isColumnEntry }
299
+ columnEntry = { navItems: [navItem], columnMap: navItem.isPotentialColumn }
283
300
  columnEntries.push(columnEntry)
284
301
  } else {
285
302
  assert(navItem.level !== 1)
@@ -1,19 +1,19 @@
1
1
  export { toggleMenuModal }
2
- export { openMenuModal }
3
- export { keepMenuModalOpen }
4
2
  export { closeMenuModal }
5
- export { coseMenuModalOnMouseLeave }
3
+ // Hover handling
4
+ export { ignoreHoverOnTouchStart }
5
+ export { openMenuModalOnMouseEnter }
6
+ export { keepMenuModalOpenOnMouseOver }
7
+ export { closeMenuModalOnMouseLeave }
8
+ export { closeMenuModalOnMouseLeaveToggle }
6
9
 
7
- import { containerQueryMobileMenu } from '../Layout'
10
+ import { viewTablet } from '../Layout'
8
11
  import { getHydrationPromise } from '../renderer/getHydrationPromise'
9
12
  import { getViewportWidth } from '../utils/getViewportWidth'
10
13
  import { isBrowser } from '../utils/isBrowser'
11
14
 
12
15
  initScrollListener()
13
16
 
14
- function keepMenuModalOpen() {
15
- open()
16
- }
17
17
  function openMenuModal(menuNavigationId: number) {
18
18
  open(menuNavigationId)
19
19
  }
@@ -72,7 +72,8 @@ let toggleLock:
72
72
  timeoutAction: NodeJS.Timeout
73
73
  }
74
74
  | undefined
75
- function coseMenuModalOnMouseLeave(menuId: number) {
75
+ function closeMenuModalOnMouseLeaveToggle(menuId: number) {
76
+ if (ignoreHover()) return
76
77
  clearTimeout(toggleLock?.timeoutAction)
77
78
  const timeoutAction = setTimeout(action, 100)
78
79
  toggleLock = {
@@ -111,7 +112,7 @@ function toggleMenuModal(menuId: number) {
111
112
  closeMenuModal()
112
113
  } else {
113
114
  openMenuModal(menuId)
114
- if (getViewportWidth() < containerQueryMobileMenu) autoScroll()
115
+ if (isMobileNav()) autoScroll()
115
116
  }
116
117
  }
117
118
 
@@ -137,3 +138,29 @@ function findCollapsibleEl(navLink: HTMLElement | undefined) {
137
138
  }
138
139
  return null
139
140
  }
141
+
142
+ function closeMenuModalOnMouseLeave() {
143
+ if (ignoreHover()) return
144
+ closeMenuModal()
145
+ }
146
+ function keepMenuModalOpenOnMouseOver() {
147
+ if (ignoreHover()) return
148
+ open()
149
+ }
150
+
151
+ let isTouchStart: ReturnType<typeof setTimeout> | undefined
152
+ function ignoreHoverOnTouchStart() {
153
+ isTouchStart = setTimeout(() => {
154
+ isTouchStart = undefined
155
+ }, 1000)
156
+ }
157
+ function openMenuModalOnMouseEnter(menuId: number) {
158
+ if (ignoreHover()) return
159
+ openMenuModal(menuId)
160
+ }
161
+ function ignoreHover() {
162
+ return isTouchStart || isMobileNav()
163
+ }
164
+ function isMobileNav() {
165
+ return getViewportWidth() <= viewTablet
166
+ }
package/MenuModal.tsx CHANGED
@@ -3,14 +3,14 @@ export { MenuModal }
3
3
  import React from 'react'
4
4
  import { usePageContext } from './renderer/usePageContext'
5
5
  import { css } from './utils/css'
6
- import { containerQueryMobileLayout, containerQueryMobileMenu } from './Layout'
6
+ import { bodyMaxWidth, viewDesktop, viewTablet } from './Layout'
7
7
  import { ExternalLinks } from './ExternalLinks'
8
8
  import { Style } from './utils/Style'
9
9
  import { NavigationWithColumnLayout } from './MenuModal/NavigationWithColumnLayout'
10
- import { closeMenuModal, keepMenuModalOpen } from './MenuModal/toggleMenuModal'
10
+ import { closeMenuModal, closeMenuModalOnMouseLeave, keepMenuModalOpenOnMouseOver } from './MenuModal/toggleMenuModal'
11
11
  import { EditLink } from './EditLink'
12
12
 
13
- function MenuModal({ isTopNav }: { isTopNav: boolean }) {
13
+ function MenuModal({ isTopNav, isNavLeftAlwaysHidden_ }: { isTopNav: boolean; isNavLeftAlwaysHidden_: boolean }) {
14
14
  return (
15
15
  <>
16
16
  <Style>{getStyle()}</Style>
@@ -21,14 +21,18 @@ function MenuModal({ isTopNav }: { isTopNav: boolean }) {
21
21
  position: isTopNav ? 'absolute' : 'fixed',
22
22
  width: '100%',
23
23
  top: 'var(--nav-head-height)',
24
- left: 0,
25
24
  zIndex: 199, // maximum value, because docsearch's modal has `z-index: 200`
26
25
  background: '#ededef',
27
26
  transitionProperty: 'opacity',
28
27
  transitionTimingFunction: 'ease',
28
+ maxWidth: isNavLeftAlwaysHidden_ ? undefined : bodyMaxWidth,
29
+ // Horizontal align
30
+ // https://stackoverflow.com/questions/3157372/css-horizontal-centering-of-a-fixed-div/32694476#32694476
31
+ left: '50%',
32
+ transform: 'translateX(-50%)',
29
33
  }}
30
- onMouseOver={() => keepMenuModalOpen()}
31
- onMouseLeave={closeMenuModal}
34
+ onMouseOver={keepMenuModalOpenOnMouseOver}
35
+ onMouseLeave={closeMenuModalOnMouseLeave}
32
36
  >
33
37
  <div
34
38
  id="menu-modal-scroll-container"
@@ -50,7 +54,9 @@ function MenuModal({ isTopNav }: { isTopNav: boolean }) {
50
54
  >
51
55
  <ExternalLinks style={{ height: 50 }} />
52
56
  </div>
53
- <EditLink style={{ justifyContent: 'center', padding: 10, marginTop: 0, marginBottom: 13 }} />
57
+ <Center>
58
+ <EditLink style={{ justifyContent: 'center', marginTop: 8, marginBottom: 20 }} verbose />
59
+ </Center>
54
60
  </div>
55
61
  </div>
56
62
  <CloseButton className="show-only-on-mobile" />
@@ -64,7 +70,7 @@ function BorderBottom() {
64
70
  <div
65
71
  id="border-bottom"
66
72
  style={{
67
- background: '#fff',
73
+ background: 'var(--color-bg-white)',
68
74
  height: 'var(--block-margin)',
69
75
  width: '100%',
70
76
  }}
@@ -79,7 +85,7 @@ function Nav() {
79
85
 
80
86
  function getStyle() {
81
87
  return css`
82
- @media(min-width: ${containerQueryMobileMenu + 1}px) {
88
+ @media(min-width: ${viewTablet + 1}px) {
83
89
  #menu-modal-scroll-container {
84
90
  max-height: calc(100vh - var(--nav-head-height) - var(--block-margin));
85
91
  ${/* https://github.com/brillout/docpress/issues/23 */ ''}
@@ -99,7 +105,7 @@ function getStyle() {
99
105
  display: none !important;
100
106
  }
101
107
  }
102
- @media(max-width: ${containerQueryMobileMenu}px) {
108
+ @media(max-width: ${viewTablet}px) {
103
109
  #menu-modal-scroll-container {
104
110
  ${/* Fallback for Firefox: it doesn't support `dvh` yet: https://caniuse.com/?search=dvh */ ''}
105
111
  ${/* Let's always and systematically use `dvh` instead of `vh` once Firefox supports it */ ''}
@@ -132,10 +138,13 @@ function getStyle() {
132
138
  .show-only-on-desktop {
133
139
  display: none !important;
134
140
  }
141
+ .columns-wrapper {
142
+ width: 100% !important;
143
+ }
135
144
  }
136
145
 
137
146
  ${/* Hide same-page headings navigation */ ''}
138
- @container container-viewport (min-width: ${containerQueryMobileLayout}px) {
147
+ @container container-viewport (min-width: ${viewDesktop}px) {
139
148
  #menu-modal-wrapper .nav-item-level-3 {
140
149
  display: none;
141
150
  }
@@ -173,3 +182,17 @@ function CloseButton({ className }: { className: string }) {
173
182
  </div>
174
183
  )
175
184
  }
185
+
186
+ function Center({ style, ...props }: any) {
187
+ return (
188
+ <div
189
+ style={{
190
+ display: 'flex',
191
+ justifyContent: 'center',
192
+ alignItems: 'center',
193
+ ...style,
194
+ }}
195
+ {...props}
196
+ ></div>
197
+ )
198
+ }
@@ -36,11 +36,11 @@
36
36
  padding-left: 9px;
37
37
  padding-top: 6px;
38
38
  padding-bottom: 6px;
39
- border-left: 3px solid var(--category-color);
39
+ border-left: 3px solid var(--color-category);
40
40
  .collapsible-expanded &,
41
41
  .collapsible-collapsed & {
42
42
  text-decoration: underline;
43
- text-decoration-color: var(--category-color);
43
+ text-decoration-color: var(--color-category);
44
44
  text-decoration-thickness: 3px;
45
45
  text-underline-offset: .3em;
46
46
  border: 0;
@@ -53,7 +53,7 @@
53
53
  }
54
54
  }
55
55
  #nav-left & {
56
- border-bottom: 3px solid var(--category-color);
56
+ border-bottom: 3px solid var(--color-category);
57
57
  padding-bottom: 2px;
58
58
  margin-top: 3px;
59
59
  margin-bottom: 10px;
@@ -86,11 +86,14 @@
86
86
  --shadow-size-minus: calc(-1 * var(--shadow-size));
87
87
  --shadow-top: inset 0px var(--shadow-size) var(--shadow-size) var(--shadow-size-minus) var(--shadow-color);
88
88
  --shadow-bottom: inset 0px var(--shadow-size-minus) var(--shadow-size) var(--shadow-size-minus) var(--shadow-color);
89
- --box-shadow-left: inset var(--shadow-size-minus) 0px var(--shadow-size) var(--shadow-size-minus) var(--shadow-color);
90
- --box-shadow-right: inset var(--shadow-size) 0px var(--shadow-size) var(--shadow-size-minus) var(--shadow-color);
91
89
  --box-shadow-top: 0 0;
92
90
  --box-shadow-bottom: 0 0;
91
+ /*
92
+ --box-shadow-left: inset var(--shadow-size-minus) 0px var(--shadow-size) var(--shadow-size-minus) var(--shadow-color);
93
+ --box-shadow-right: inset var(--shadow-size) 0px var(--shadow-size) var(--shadow-size-minus) var(--shadow-color);
93
94
  box-shadow: var(--box-shadow-top), var(--box-shadow-bottom), var(--box-shadow-left), var(--box-shadow-right);
95
+ */
96
+ box-shadow: var(--box-shadow-top), var(--box-shadow-bottom);
94
97
  }
95
98
  .nav-item-level-3.nav-item-first-of-its-kind {
96
99
  padding-top: 10px;
@@ -105,14 +108,16 @@
105
108
  position: relative;
106
109
  }
107
110
 
108
- .nav-item-level-3::before,
109
- .nav-item-level-2.is-active::before {
110
- background-color: var(--active-color);
111
- position: absolute;
112
- height: 100%;
113
- width: 100%;
114
- top: 0;
115
- left: 0;
116
- z-index: 1;
117
- content: '';
111
+ #nav-left {
112
+ .nav-item-level-1 {
113
+ margin-left: min(25px, max(5px, 30 * (1vw - 12.7px) - 5px));
114
+ }
115
+ .nav-head-logo {
116
+ padding-left: min(25px, max(5px, 30 * (1vw - 12.7px) - 5px));
117
+ }
118
+ .nav-item-level-2,
119
+ .nav-item-level-3,
120
+ .nav-item-level-4 {
121
+ padding-left: min(30px, max(8px, 30 * (1vw - 12.7px)));
122
+ }
118
123
  }
@@ -31,8 +31,22 @@ type NavItem = {
31
31
  title: string
32
32
  titleInNav: string
33
33
  menuModalFullWidth?: true
34
- isColumnEntry?: ColumnMap
34
+ /**
35
+ * Maps viewport column counts to column indices.
36
+ * Indicates this nav item is a "column entry" (a level-1 or level-4 heading that starts a new column section).
37
+ * Example: `{ 1: 0, 2: 1, 3: 0 }` means:
38
+ * - When there's 1 column, put this item in column 0
39
+ * - When there are 2 columns, put it in column 1
40
+ * - When there are 3 columns, put it in column 0
41
+ */
42
+ isPotentialColumn?: ColumnMap
35
43
  }
44
+ /**
45
+ * A mapping of viewport column counts to column indices.
46
+ * Used to determine which column a nav item should be placed in for different viewport widths.
47
+ * Key: number of columns in the viewport
48
+ * Value: the column index (0-based) where this item should be placed
49
+ */
36
50
  type ColumnMap = Record<number, number>
37
51
 
38
52
  type PropsNavItem = PropsAnchor & PropsSpan
@@ -54,7 +68,7 @@ function NavItemComponent({
54
68
  const icon = navItem.titleIcon && (
55
69
  <img
56
70
  src={navItem.titleIcon}
57
- style={{ height: iconSize, width: iconSize, marginRight: 8, marginLeft: 4, ...navItem.titleIconStyle }}
71
+ style={{ height: iconSize, width: iconSize, marginRight: 8, marginLeft: 2, ...navItem.titleIconStyle }}
58
72
  />
59
73
  )
60
74
 
@@ -92,7 +106,7 @@ function NavItemComponent({
92
106
  className: [
93
107
  'nav-item',
94
108
  'nav-item-level-' + navItem.level,
95
- navItem.url && navItem.isActive && ' is-active',
109
+ ((navItem.url && navItem.isActive) || navItem.level === 3) && ' is-active',
96
110
  navItem.isFirstOfItsKind && 'nav-item-first-of-its-kind',
97
111
  navItem.isLastOfItsKind && 'nav-item-last-of-its-kind',
98
112
  ]
@@ -101,7 +115,7 @@ function NavItemComponent({
101
115
  }
102
116
  if (navItem.level === 1) {
103
117
  props.style = {
104
- ['--category-color']: navItem.color!,
118
+ ['--color-category']: navItem.color!,
105
119
  }
106
120
  }
107
121
 
@@ -40,7 +40,7 @@ function CopyButton() {
40
40
  <button
41
41
  className="copy-button raised"
42
42
  aria-label={tooltip}
43
- data-label-position="top"
43
+ data-label-position="top-left"
44
44
  type="button"
45
45
  onClick={onClick}
46
46
  >
@@ -7,9 +7,14 @@
7
7
  .colorize-on-hover:hover [class*=' decolorize-'] {
8
8
  filter: grayscale(0) opacity(1) !important;
9
9
  }
10
+ .is-active {
11
+ background-color: var(--color-active);
12
+ }
10
13
  .link-hover-animation a:hover {
11
- color: black !important;
12
- background-color: var(--active-color);
14
+ background-color: var(--color-active);
15
+ &.is-active {
16
+ background-color: var(--color-active-double);
17
+ }
13
18
  }
14
19
  }
15
20
 
@@ -46,5 +51,7 @@
46
51
  }
47
52
 
48
53
  body {
49
- --active-color: rgba(0, 0, 0, 0.03);
54
+ --color-active-value: 0.03;
55
+ --color-active: rgba(0, 0, 0, var(--color-active-value));
56
+ --color-active-double: rgba(0, 0, 0, calc(2 * var(--color-active-value)));
50
57
  }
package/css/heading.css CHANGED
@@ -77,7 +77,13 @@ h3 {
77
77
  .doc-page h3[id]:hover::before {
78
78
  content: '#';
79
79
  position: absolute;
80
- left: calc(-1 * (0.75em - 26px));
80
+ height: 100%;
81
+ line-height: 0;
82
+ font-size: 23px;
83
+ top: 1px;
84
+ left: calc(-1 * (0.75em - var(--hash-offset)));
81
85
  color: #aaa;
86
+ display: flex;
87
+ align-items: center;
82
88
  }
83
89
  }
package/css/tooltip.css CHANGED
@@ -37,9 +37,15 @@
37
37
  top: 100%;
38
38
  margin-top: 5px;
39
39
  }
40
- /* Show above */
41
- [aria-label][data-label-position='top']::before {
40
+ /* Show above-left */
41
+ /* Used by:
42
+ * - Copy-to-clipboard button
43
+ * - <UsedBy> component for Vike's landing page
44
+ */
45
+ [aria-label][data-label-position='top-left']::before {
42
46
  bottom: 100%;
43
47
  margin-bottom: 7px;
48
+ margin-left: calc(50% + 13px);
49
+ transform: translate(-100%, 0);
44
50
  }
45
51
  }