@netfoundry/docusaurus-theme 0.6.0 → 0.8.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.
- package/css/layout.css +5 -1
- package/css/product-picker.css +210 -0
- package/css/vars.css +8 -0
- package/dist/css/layout.css +5 -1
- package/dist/css/product-picker.css +210 -0
- package/dist/css/vars.css +8 -0
- package/dist/src/options.d.ts +32 -4
- package/dist/src/options.d.ts.map +1 -1
- package/dist/theme/Layout/index.d.ts.map +1 -1
- package/dist/theme/Layout/index.js +6 -6
- package/dist/theme/Layout/index.js.map +1 -1
- package/dist/theme/NavbarItem/ComponentTypes.d.ts +6 -0
- package/dist/theme/NavbarItem/ComponentTypes.d.ts.map +1 -0
- package/dist/theme/NavbarItem/ComponentTypes.js +13 -0
- package/dist/theme/NavbarItem/ComponentTypes.js.map +1 -0
- package/dist/theme/NavbarItem/DropdownNavbarItem/Desktop/index.d.ts +2 -0
- package/dist/theme/NavbarItem/DropdownNavbarItem/Desktop/index.d.ts.map +1 -0
- package/dist/theme/NavbarItem/DropdownNavbarItem/Desktop/index.js +82 -0
- package/dist/theme/NavbarItem/DropdownNavbarItem/Desktop/index.js.map +1 -0
- package/dist/theme/NavbarItem/types/ProductPicker/index.d.ts +20 -0
- package/dist/theme/NavbarItem/types/ProductPicker/index.d.ts.map +1 -0
- package/dist/theme/NavbarItem/types/ProductPicker/index.js +91 -0
- package/dist/theme/NavbarItem/types/ProductPicker/index.js.map +1 -0
- package/package.json +7 -2
- package/theme/Layout/index.tsx +9 -8
- package/theme/NavbarItem/ComponentTypes.tsx +14 -0
- package/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx +125 -0
- package/theme/NavbarItem/types/ProductPicker/index.tsx +156 -0
- package/theme/docusaurus-theme-modules.d.ts +32 -0
- package/css/.claude/settings.local.json +0 -12
package/css/layout.css
CHANGED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* =========================================================================
|
|
2
|
+
PRODUCT PICKER
|
|
3
|
+
Styles for the Products product-picker dropdown in the navbar.
|
|
4
|
+
|
|
5
|
+
Import in docusaurus.config.ts customCss:
|
|
6
|
+
require.resolve('@netfoundry/docusaurus-theme/css/product-picker.css')
|
|
7
|
+
========================================================================= */
|
|
8
|
+
|
|
9
|
+
/* ── Product / resource icons inside the picker ─────────────────────────── */
|
|
10
|
+
.picker-logo { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; margin-right: 0.8rem; }
|
|
11
|
+
.picker-icon { width: 20px; height: 20px; margin-right: 0.8rem; background-color: var(--ifm-color-primary); -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; display: inline-block; }
|
|
12
|
+
|
|
13
|
+
/* Light/dark logo switching */
|
|
14
|
+
.picker-logo--dark { display: none; }
|
|
15
|
+
[data-theme='dark'] .picker-logo--light { display: none; }
|
|
16
|
+
[data-theme='dark'] .picker-logo--dark { display: block; }
|
|
17
|
+
|
|
18
|
+
/* zrok logo needs a subtle shadow in light mode (white logo on white bg) */
|
|
19
|
+
[data-theme='light'] img[src*="zrok-logo"] { filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.55)); }
|
|
20
|
+
|
|
21
|
+
/* ── Dropdown trigger button in the navbar ──────────────────────────────── */
|
|
22
|
+
.nf-picker-trigger,
|
|
23
|
+
.nf-resources-dropdown {
|
|
24
|
+
color: var(--ifm-font-color-base);
|
|
25
|
+
position: relative;
|
|
26
|
+
padding-right: 1.2rem;
|
|
27
|
+
transition: color 0.3s ease;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.nf-picker-trigger:hover,
|
|
31
|
+
.nf-resources-dropdown:hover,
|
|
32
|
+
.navbar__item.dropdown--show .nf-picker-trigger,
|
|
33
|
+
.navbar__item.dropdown--show .nf-resources-dropdown {
|
|
34
|
+
color: var(--ifm-color-primary);
|
|
35
|
+
text-decoration: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Animated chevron */
|
|
39
|
+
.nf-picker-trigger::after,
|
|
40
|
+
.nf-resources-dropdown::after {
|
|
41
|
+
content: '';
|
|
42
|
+
position: absolute;
|
|
43
|
+
right: 0.2rem;
|
|
44
|
+
top: 50%;
|
|
45
|
+
transform: translateY(-50%);
|
|
46
|
+
width: 0;
|
|
47
|
+
height: 0;
|
|
48
|
+
border-left: 4px solid transparent;
|
|
49
|
+
border-right: 4px solid transparent;
|
|
50
|
+
border-top: 5px solid currentColor;
|
|
51
|
+
transition: all 0.3s ease;
|
|
52
|
+
opacity: 0.6;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Hover: chevron drops slightly */
|
|
56
|
+
.navbar__link.nf-picker-trigger:hover::after,
|
|
57
|
+
.navbar__link.nf-resources-dropdown:hover::after {
|
|
58
|
+
transform: translateY(-20%);
|
|
59
|
+
opacity: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Open: chevron rotates 180° */
|
|
63
|
+
.nf-picker--open .nf-picker-trigger::after,
|
|
64
|
+
.navbar__item.dropdown--show .nf-resources-dropdown::after {
|
|
65
|
+
transform: translateY(-50%) rotate(180deg);
|
|
66
|
+
opacity: 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Dark mode: cyan accent on hover */
|
|
70
|
+
[data-theme='dark'] .nf-picker-trigger:hover,
|
|
71
|
+
[data-theme='dark'] .nf-resources-dropdown:hover,
|
|
72
|
+
[data-theme='dark'] .nf-picker--open .nf-picker-trigger,
|
|
73
|
+
[data-theme='dark'] .navbar__item.dropdown--show .nf-resources-dropdown {
|
|
74
|
+
color: #22d3ee;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ── ProductPicker panel (custom navbar item) ───────────────────────────── */
|
|
78
|
+
.nf-picker-panel {
|
|
79
|
+
position: fixed;
|
|
80
|
+
top: 3.75rem;
|
|
81
|
+
left: 50%;
|
|
82
|
+
transform: translateX(-50%);
|
|
83
|
+
width: 85vw;
|
|
84
|
+
max-width: 1000px;
|
|
85
|
+
padding: 1.5rem 2rem;
|
|
86
|
+
border-radius: 12px;
|
|
87
|
+
border: 1px solid rgba(0, 118, 255, 0.12);
|
|
88
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
|
|
89
|
+
background: var(--ifm-card-background-color);
|
|
90
|
+
z-index: 1000;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* CSS bridge: transparent strip above the panel eats the gap */
|
|
94
|
+
.nf-picker-panel::before {
|
|
95
|
+
content: '';
|
|
96
|
+
display: block;
|
|
97
|
+
position: absolute;
|
|
98
|
+
top: -20px;
|
|
99
|
+
left: 0;
|
|
100
|
+
right: 0;
|
|
101
|
+
height: 20px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ── Legacy html-type dropdown panel ────────────────────────────────────── */
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
* CSS bridge: an invisible pseudo-element that extends the panel's hit area
|
|
108
|
+
* upward by 20 px. When the cursor moves from the trigger button down into
|
|
109
|
+
* this transparent strip, mouseenter fires on the panel (ul) immediately —
|
|
110
|
+
* cancelling the hide timer before it expires — so the menu never flickers
|
|
111
|
+
* during diagonal movement across the gap between navbar and panel.
|
|
112
|
+
*/
|
|
113
|
+
.dropdown__menu:has(.picker-content)::before {
|
|
114
|
+
content: '';
|
|
115
|
+
display: block;
|
|
116
|
+
position: absolute;
|
|
117
|
+
top: -20px;
|
|
118
|
+
left: 0;
|
|
119
|
+
right: 0;
|
|
120
|
+
height: 20px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* :has() raises specificity above plain .dropdown__menu — no !important needed */
|
|
124
|
+
.dropdown__menu:has(.picker-content) {
|
|
125
|
+
position: fixed;
|
|
126
|
+
top: 3.75rem;
|
|
127
|
+
left: 50%;
|
|
128
|
+
transform: translateX(-50%);
|
|
129
|
+
width: 85vw;
|
|
130
|
+
max-width: 1000px;
|
|
131
|
+
padding: 1.5rem 2rem;
|
|
132
|
+
border-radius: 12px;
|
|
133
|
+
border: 1px solid rgba(0, 118, 255, 0.12);
|
|
134
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
|
|
135
|
+
background: var(--ifm-card-background-color);
|
|
136
|
+
z-index: 1000;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Narrower panel for the 2-column Resources menu */
|
|
140
|
+
.dropdown__menu:has(.picker-resources) { max-width: 700px; }
|
|
141
|
+
|
|
142
|
+
/* Ensure visibility when open */
|
|
143
|
+
.dropdown--show > .dropdown__menu,
|
|
144
|
+
.dropdown:hover > .dropdown__menu {
|
|
145
|
+
display: block;
|
|
146
|
+
visibility: visible;
|
|
147
|
+
opacity: 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* ── Grid layout ────────────────────────────────────────────────────────── */
|
|
151
|
+
.picker-content { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 2rem; }
|
|
152
|
+
.picker-resources { grid-template-columns: 1fr 1fr; }
|
|
153
|
+
|
|
154
|
+
/* ── Column headers ─────────────────────────────────────────────────────── */
|
|
155
|
+
.picker-header {
|
|
156
|
+
font-size: 0.7rem;
|
|
157
|
+
font-weight: 900;
|
|
158
|
+
color: #94a3b8;
|
|
159
|
+
text-transform: uppercase;
|
|
160
|
+
letter-spacing: 0.1em;
|
|
161
|
+
border-bottom: 2px solid rgba(148, 163, 184, 0.2);
|
|
162
|
+
display: block;
|
|
163
|
+
padding-bottom: 0.5rem;
|
|
164
|
+
margin-bottom: 0.75rem;
|
|
165
|
+
}
|
|
166
|
+
.picker-header--nf-primary { color: var(--nf-primary); border-bottom-color: rgba(var(--nf-color-primary), 0.3); }
|
|
167
|
+
.picker-header--nf-secondary { color: var(--nf-secondary); border-bottom-color: rgba(var(--nf-color-secondary), 0.3); }
|
|
168
|
+
.picker-header--nf-tertiary { color: var(--nf-tertiary); border-bottom-color: rgba(var(--nf-color-tertiary), 0.3); }
|
|
169
|
+
[data-theme='light'] .picker-header { filter: brightness(0.6); }
|
|
170
|
+
[data-theme='dark'] .picker-header--nf-tertiary { filter: brightness(1.6); }
|
|
171
|
+
|
|
172
|
+
/* ── Product / resource links ───────────────────────────────────────────── */
|
|
173
|
+
.picker-link {
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: flex-start;
|
|
176
|
+
gap: 0.75rem;
|
|
177
|
+
padding: 0.55rem 0.65rem;
|
|
178
|
+
text-decoration: none;
|
|
179
|
+
transition: all 0.2s ease;
|
|
180
|
+
border-radius: 8px;
|
|
181
|
+
}
|
|
182
|
+
.picker-link:hover { background: rgba(0, 118, 255, 0.06); transform: translateX(3px); }
|
|
183
|
+
.picker-link strong { color: var(--ifm-font-color-base); font-size: 0.95rem; font-weight: 900; letter-spacing: -0.02em; display: block; }
|
|
184
|
+
.picker-link span { color: #64748b; font-size: 0.82rem; display: block; margin-top: 2px; line-height: 1.35; }
|
|
185
|
+
|
|
186
|
+
/* ── Mobile (<= 996 px) ─────────────────────────────────────────────────── */
|
|
187
|
+
@media (max-width: 996px) {
|
|
188
|
+
/* Fixed grid makes no sense in the narrow sidebar — hide the raw HTML blob */
|
|
189
|
+
.menu__list .menu__list-item:has(> .picker-content) { display: none; }
|
|
190
|
+
|
|
191
|
+
/* Docusaurus already renders a chevron; hide ours to avoid doubles */
|
|
192
|
+
.nf-picker-trigger::after,
|
|
193
|
+
.nf-resources-dropdown::after { display: none; }
|
|
194
|
+
|
|
195
|
+
.nf-picker-trigger,
|
|
196
|
+
.nf-resources-dropdown { padding-right: 0.5rem; }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* ── Desktop (>= 997 px) ────────────────────────────────────────────────── */
|
|
200
|
+
@media (min-width: 997px) {
|
|
201
|
+
/* Hide mobile-only fallback links when product picker is active */
|
|
202
|
+
.dropdown__menu .mobile-nav-link { display: none; }
|
|
203
|
+
|
|
204
|
+
/* Force panel visible when JS sets dropdown--show (swizzled component) */
|
|
205
|
+
.navbar__item.dropdown--show .dropdown__menu:has(.picker-content) {
|
|
206
|
+
display: block;
|
|
207
|
+
visibility: visible;
|
|
208
|
+
opacity: 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
package/css/vars.css
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
:root {
|
|
2
2
|
--ifm-navbar-height: 50px;
|
|
3
3
|
--nf-docs-max-width: 1400px;
|
|
4
|
+
|
|
5
|
+
--nf-color-primary: 119, 194, 252;
|
|
6
|
+
--nf-color-secondary: 78, 219, 63;
|
|
7
|
+
--nf-color-tertiary: 3, 92, 230;
|
|
8
|
+
|
|
9
|
+
--nf-primary: rgb(var(--nf-color-primary));
|
|
10
|
+
--nf-secondary: rgb(var(--nf-color-secondary));
|
|
11
|
+
--nf-tertiary: rgb(var(--nf-color-tertiary));
|
|
4
12
|
/*--nf-docs-main-color: purple;*/
|
|
5
13
|
.container {
|
|
6
14
|
/*background: sandybrown;*/
|
package/dist/css/layout.css
CHANGED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* =========================================================================
|
|
2
|
+
PRODUCT PICKER
|
|
3
|
+
Styles for the Products product-picker dropdown in the navbar.
|
|
4
|
+
|
|
5
|
+
Import in docusaurus.config.ts customCss:
|
|
6
|
+
require.resolve('@netfoundry/docusaurus-theme/css/product-picker.css')
|
|
7
|
+
========================================================================= */
|
|
8
|
+
|
|
9
|
+
/* ── Product / resource icons inside the picker ─────────────────────────── */
|
|
10
|
+
.picker-logo { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; margin-right: 0.8rem; }
|
|
11
|
+
.picker-icon { width: 20px; height: 20px; margin-right: 0.8rem; background-color: var(--ifm-color-primary); -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; display: inline-block; }
|
|
12
|
+
|
|
13
|
+
/* Light/dark logo switching */
|
|
14
|
+
.picker-logo--dark { display: none; }
|
|
15
|
+
[data-theme='dark'] .picker-logo--light { display: none; }
|
|
16
|
+
[data-theme='dark'] .picker-logo--dark { display: block; }
|
|
17
|
+
|
|
18
|
+
/* zrok logo needs a subtle shadow in light mode (white logo on white bg) */
|
|
19
|
+
[data-theme='light'] img[src*="zrok-logo"] { filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.55)); }
|
|
20
|
+
|
|
21
|
+
/* ── Dropdown trigger button in the navbar ──────────────────────────────── */
|
|
22
|
+
.nf-picker-trigger,
|
|
23
|
+
.nf-resources-dropdown {
|
|
24
|
+
color: var(--ifm-font-color-base);
|
|
25
|
+
position: relative;
|
|
26
|
+
padding-right: 1.2rem;
|
|
27
|
+
transition: color 0.3s ease;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.nf-picker-trigger:hover,
|
|
31
|
+
.nf-resources-dropdown:hover,
|
|
32
|
+
.navbar__item.dropdown--show .nf-picker-trigger,
|
|
33
|
+
.navbar__item.dropdown--show .nf-resources-dropdown {
|
|
34
|
+
color: var(--ifm-color-primary);
|
|
35
|
+
text-decoration: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Animated chevron */
|
|
39
|
+
.nf-picker-trigger::after,
|
|
40
|
+
.nf-resources-dropdown::after {
|
|
41
|
+
content: '';
|
|
42
|
+
position: absolute;
|
|
43
|
+
right: 0.2rem;
|
|
44
|
+
top: 50%;
|
|
45
|
+
transform: translateY(-50%);
|
|
46
|
+
width: 0;
|
|
47
|
+
height: 0;
|
|
48
|
+
border-left: 4px solid transparent;
|
|
49
|
+
border-right: 4px solid transparent;
|
|
50
|
+
border-top: 5px solid currentColor;
|
|
51
|
+
transition: all 0.3s ease;
|
|
52
|
+
opacity: 0.6;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Hover: chevron drops slightly */
|
|
56
|
+
.navbar__link.nf-picker-trigger:hover::after,
|
|
57
|
+
.navbar__link.nf-resources-dropdown:hover::after {
|
|
58
|
+
transform: translateY(-20%);
|
|
59
|
+
opacity: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Open: chevron rotates 180° */
|
|
63
|
+
.nf-picker--open .nf-picker-trigger::after,
|
|
64
|
+
.navbar__item.dropdown--show .nf-resources-dropdown::after {
|
|
65
|
+
transform: translateY(-50%) rotate(180deg);
|
|
66
|
+
opacity: 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Dark mode: cyan accent on hover */
|
|
70
|
+
[data-theme='dark'] .nf-picker-trigger:hover,
|
|
71
|
+
[data-theme='dark'] .nf-resources-dropdown:hover,
|
|
72
|
+
[data-theme='dark'] .nf-picker--open .nf-picker-trigger,
|
|
73
|
+
[data-theme='dark'] .navbar__item.dropdown--show .nf-resources-dropdown {
|
|
74
|
+
color: #22d3ee;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ── ProductPicker panel (custom navbar item) ───────────────────────────── */
|
|
78
|
+
.nf-picker-panel {
|
|
79
|
+
position: fixed;
|
|
80
|
+
top: 3.75rem;
|
|
81
|
+
left: 50%;
|
|
82
|
+
transform: translateX(-50%);
|
|
83
|
+
width: 85vw;
|
|
84
|
+
max-width: 1000px;
|
|
85
|
+
padding: 1.5rem 2rem;
|
|
86
|
+
border-radius: 12px;
|
|
87
|
+
border: 1px solid rgba(0, 118, 255, 0.12);
|
|
88
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
|
|
89
|
+
background: var(--ifm-card-background-color);
|
|
90
|
+
z-index: 1000;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* CSS bridge: transparent strip above the panel eats the gap */
|
|
94
|
+
.nf-picker-panel::before {
|
|
95
|
+
content: '';
|
|
96
|
+
display: block;
|
|
97
|
+
position: absolute;
|
|
98
|
+
top: -20px;
|
|
99
|
+
left: 0;
|
|
100
|
+
right: 0;
|
|
101
|
+
height: 20px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ── Legacy html-type dropdown panel ────────────────────────────────────── */
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
* CSS bridge: an invisible pseudo-element that extends the panel's hit area
|
|
108
|
+
* upward by 20 px. When the cursor moves from the trigger button down into
|
|
109
|
+
* this transparent strip, mouseenter fires on the panel (ul) immediately —
|
|
110
|
+
* cancelling the hide timer before it expires — so the menu never flickers
|
|
111
|
+
* during diagonal movement across the gap between navbar and panel.
|
|
112
|
+
*/
|
|
113
|
+
.dropdown__menu:has(.picker-content)::before {
|
|
114
|
+
content: '';
|
|
115
|
+
display: block;
|
|
116
|
+
position: absolute;
|
|
117
|
+
top: -20px;
|
|
118
|
+
left: 0;
|
|
119
|
+
right: 0;
|
|
120
|
+
height: 20px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* :has() raises specificity above plain .dropdown__menu — no !important needed */
|
|
124
|
+
.dropdown__menu:has(.picker-content) {
|
|
125
|
+
position: fixed;
|
|
126
|
+
top: 3.75rem;
|
|
127
|
+
left: 50%;
|
|
128
|
+
transform: translateX(-50%);
|
|
129
|
+
width: 85vw;
|
|
130
|
+
max-width: 1000px;
|
|
131
|
+
padding: 1.5rem 2rem;
|
|
132
|
+
border-radius: 12px;
|
|
133
|
+
border: 1px solid rgba(0, 118, 255, 0.12);
|
|
134
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
|
|
135
|
+
background: var(--ifm-card-background-color);
|
|
136
|
+
z-index: 1000;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Narrower panel for the 2-column Resources menu */
|
|
140
|
+
.dropdown__menu:has(.picker-resources) { max-width: 700px; }
|
|
141
|
+
|
|
142
|
+
/* Ensure visibility when open */
|
|
143
|
+
.dropdown--show > .dropdown__menu,
|
|
144
|
+
.dropdown:hover > .dropdown__menu {
|
|
145
|
+
display: block;
|
|
146
|
+
visibility: visible;
|
|
147
|
+
opacity: 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* ── Grid layout ────────────────────────────────────────────────────────── */
|
|
151
|
+
.picker-content { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 2rem; }
|
|
152
|
+
.picker-resources { grid-template-columns: 1fr 1fr; }
|
|
153
|
+
|
|
154
|
+
/* ── Column headers ─────────────────────────────────────────────────────── */
|
|
155
|
+
.picker-header {
|
|
156
|
+
font-size: 0.7rem;
|
|
157
|
+
font-weight: 900;
|
|
158
|
+
color: #94a3b8;
|
|
159
|
+
text-transform: uppercase;
|
|
160
|
+
letter-spacing: 0.1em;
|
|
161
|
+
border-bottom: 2px solid rgba(148, 163, 184, 0.2);
|
|
162
|
+
display: block;
|
|
163
|
+
padding-bottom: 0.5rem;
|
|
164
|
+
margin-bottom: 0.75rem;
|
|
165
|
+
}
|
|
166
|
+
.picker-header--nf-primary { color: var(--nf-primary); border-bottom-color: rgba(var(--nf-color-primary), 0.3); }
|
|
167
|
+
.picker-header--nf-secondary { color: var(--nf-secondary); border-bottom-color: rgba(var(--nf-color-secondary), 0.3); }
|
|
168
|
+
.picker-header--nf-tertiary { color: var(--nf-tertiary); border-bottom-color: rgba(var(--nf-color-tertiary), 0.3); }
|
|
169
|
+
[data-theme='light'] .picker-header { filter: brightness(0.6); }
|
|
170
|
+
[data-theme='dark'] .picker-header--nf-tertiary { filter: brightness(1.6); }
|
|
171
|
+
|
|
172
|
+
/* ── Product / resource links ───────────────────────────────────────────── */
|
|
173
|
+
.picker-link {
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: flex-start;
|
|
176
|
+
gap: 0.75rem;
|
|
177
|
+
padding: 0.55rem 0.65rem;
|
|
178
|
+
text-decoration: none;
|
|
179
|
+
transition: all 0.2s ease;
|
|
180
|
+
border-radius: 8px;
|
|
181
|
+
}
|
|
182
|
+
.picker-link:hover { background: rgba(0, 118, 255, 0.06); transform: translateX(3px); }
|
|
183
|
+
.picker-link strong { color: var(--ifm-font-color-base); font-size: 0.95rem; font-weight: 900; letter-spacing: -0.02em; display: block; }
|
|
184
|
+
.picker-link span { color: #64748b; font-size: 0.82rem; display: block; margin-top: 2px; line-height: 1.35; }
|
|
185
|
+
|
|
186
|
+
/* ── Mobile (<= 996 px) ─────────────────────────────────────────────────── */
|
|
187
|
+
@media (max-width: 996px) {
|
|
188
|
+
/* Fixed grid makes no sense in the narrow sidebar — hide the raw HTML blob */
|
|
189
|
+
.menu__list .menu__list-item:has(> .picker-content) { display: none; }
|
|
190
|
+
|
|
191
|
+
/* Docusaurus already renders a chevron; hide ours to avoid doubles */
|
|
192
|
+
.nf-picker-trigger::after,
|
|
193
|
+
.nf-resources-dropdown::after { display: none; }
|
|
194
|
+
|
|
195
|
+
.nf-picker-trigger,
|
|
196
|
+
.nf-resources-dropdown { padding-right: 0.5rem; }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* ── Desktop (>= 997 px) ────────────────────────────────────────────────── */
|
|
200
|
+
@media (min-width: 997px) {
|
|
201
|
+
/* Hide mobile-only fallback links when product picker is active */
|
|
202
|
+
.dropdown__menu .mobile-nav-link { display: none; }
|
|
203
|
+
|
|
204
|
+
/* Force panel visible when JS sets dropdown--show (swizzled component) */
|
|
205
|
+
.navbar__item.dropdown--show .dropdown__menu:has(.picker-content) {
|
|
206
|
+
display: block;
|
|
207
|
+
visibility: visible;
|
|
208
|
+
opacity: 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
package/dist/css/vars.css
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
:root {
|
|
2
2
|
--ifm-navbar-height: 50px;
|
|
3
3
|
--nf-docs-max-width: 1400px;
|
|
4
|
+
|
|
5
|
+
--nf-color-primary: 119, 194, 252;
|
|
6
|
+
--nf-color-secondary: 78, 219, 63;
|
|
7
|
+
--nf-color-tertiary: 3, 92, 230;
|
|
8
|
+
|
|
9
|
+
--nf-primary: rgb(var(--nf-color-primary));
|
|
10
|
+
--nf-secondary: rgb(var(--nf-color-secondary));
|
|
11
|
+
--nf-tertiary: rgb(var(--nf-color-tertiary));
|
|
4
12
|
/*--nf-docs-main-color: purple;*/
|
|
5
13
|
.container {
|
|
6
14
|
/*background: sandybrown;*/
|
package/dist/src/options.d.ts
CHANGED
|
@@ -33,6 +33,32 @@ export interface StarBannerConfig {
|
|
|
33
33
|
repoUrl: string;
|
|
34
34
|
/** Label text for the star button */
|
|
35
35
|
label: string;
|
|
36
|
+
/** Only show banner when the current path starts with this prefix (e.g. '/docs/openziti') */
|
|
37
|
+
pathPrefix?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A single link entry in the product picker
|
|
41
|
+
*/
|
|
42
|
+
export interface ProductPickerLink {
|
|
43
|
+
/** Display name */
|
|
44
|
+
label: string;
|
|
45
|
+
/** Route or URL */
|
|
46
|
+
to: string;
|
|
47
|
+
/** Logo shown in light mode (and dark mode if logoDark is absent) */
|
|
48
|
+
logo?: string;
|
|
49
|
+
/** Logo shown only in dark mode */
|
|
50
|
+
logoDark?: string;
|
|
51
|
+
/** Short description shown beneath the label */
|
|
52
|
+
description?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A column in the product picker dropdown.
|
|
56
|
+
* Header color is assigned automatically by column index (primary → secondary → tertiary).
|
|
57
|
+
*/
|
|
58
|
+
export interface ProductPickerColumn {
|
|
59
|
+
/** Column heading text */
|
|
60
|
+
header: string;
|
|
61
|
+
links: ProductPickerLink[];
|
|
36
62
|
}
|
|
37
63
|
/**
|
|
38
64
|
* Options passed to the theme plugin in docusaurus.config.ts
|
|
@@ -75,10 +101,12 @@ export interface NetFoundryThemeOptions {
|
|
|
75
101
|
export interface NetFoundryThemeConfig {
|
|
76
102
|
/** Footer configuration */
|
|
77
103
|
footer?: FooterConfig;
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
|
|
104
|
+
/** Path-aware star banners — each entry shows only when the current path starts with pathPrefix (omit pathPrefix to show everywhere) */
|
|
105
|
+
starBanners?: StarBannerConfig[];
|
|
106
|
+
/** Product picker columns. If omitted, the theme falls back to built-in NetFoundry defaults. */
|
|
107
|
+
productPickerColumns?: ProductPickerColumn[];
|
|
108
|
+
/** Logo URL for the NetFoundry Console link in the product picker (overrides the default NetFoundry branding icon) */
|
|
109
|
+
consoleLogo?: string;
|
|
82
110
|
}
|
|
83
111
|
/**
|
|
84
112
|
* Extended Docusaurus ThemeConfig with NetFoundry configuration
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;IACjC,8BAA8B;IAC9B,cAAc,CAAC,EAAE,SAAS,EAAE,CAAC;IAC7B,8BAA8B;IAC9B,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;IACjC,8BAA8B;IAC9B,cAAc,CAAC,EAAE,SAAS,EAAE,CAAC;IAC7B,8BAA8B;IAC9B,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,6FAA6F;IAC7F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,sBAAsB;IACrC,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,qBAAqB;IACpC,2BAA2B;IAC3B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,wIAAwI;IACxI,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,gGAAgG;IAChG,oBAAoB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC7C,sHAAsH;IACtH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../theme/Layout/index.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../theme/Layout/index.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAU9C,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,EAC7B,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,WAAW,GACZ,EAAE,WAAW,GAAG,SAAS,CAyCzB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
3
|
+
import { useLocation } from 'react-router-dom';
|
|
3
4
|
import { NetFoundryLayout, defaultNetFoundryFooterProps, defaultSocialProps, } from '../../src/ui';
|
|
4
5
|
/**
|
|
5
6
|
* NetFoundry theme Layout component.
|
|
@@ -12,6 +13,7 @@ import { NetFoundryLayout, defaultNetFoundryFooterProps, defaultSocialProps, } f
|
|
|
12
13
|
*/
|
|
13
14
|
export default function Layout({ children, noFooter, wrapperClassName, title, description, }) {
|
|
14
15
|
const { siteConfig } = useDocusaurusContext();
|
|
16
|
+
const { pathname } = useLocation();
|
|
15
17
|
const themeConfig = siteConfig.themeConfig;
|
|
16
18
|
const nfConfig = themeConfig.netfoundry ?? {};
|
|
17
19
|
// Build footer props from config, falling back to defaults
|
|
@@ -25,12 +27,10 @@ export default function Layout({ children, noFooter, wrapperClassName, title, de
|
|
|
25
27
|
...nfConfig.footer?.socialProps,
|
|
26
28
|
},
|
|
27
29
|
};
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
label: nfConfig.starBanner.label,
|
|
33
|
-
}
|
|
30
|
+
// Pick the first banner whose pathPrefix matches (or has no prefix)
|
|
31
|
+
const matchedBanner = nfConfig.starBanners?.find(b => !b.pathPrefix || pathname.startsWith(b.pathPrefix));
|
|
32
|
+
const starProps = matchedBanner
|
|
33
|
+
? { repoUrl: matchedBanner.repoUrl, label: matchedBanner.label }
|
|
34
34
|
: undefined;
|
|
35
35
|
return (_jsx(NetFoundryLayout, { title: title, description: description, className: wrapperClassName, noFooter: noFooter, footerProps: footerProps, starProps: starProps, meta: {
|
|
36
36
|
siteName: siteConfig.title,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../theme/Layout/index.tsx"],"names":[],"mappings":";AACA,OAAO,oBAAoB,MAAM,kCAAkC,CAAC;AACpE,OAAO,EACL,gBAAgB,EAChB,4BAA4B,EAC5B,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAWtB;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,EAC7B,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,WAAW,GACC;IACZ,MAAM,EAAE,UAAU,EAAE,GAAG,oBAAoB,EAAE,CAAC;IAC9C,MAAM,WAAW,GAAG,UAAU,CAAC,WAAwC,CAAC;IACxE,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,IAAI,EAAE,CAAC;IAE9C,2DAA2D;IAC3D,MAAM,WAAW,GAAG,QAAQ;QAC1B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC;YACE,GAAG,4BAA4B,EAAE;YACjC,GAAG,QAAQ,CAAC,MAAM;YAClB,WAAW,EAAE;gBACX,GAAG,kBAAkB;gBACrB,GAAG,QAAQ,CAAC,MAAM,EAAE,WAAW;aAChC;SACF,CAAC;IAEN,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../theme/Layout/index.tsx"],"names":[],"mappings":";AACA,OAAO,oBAAoB,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EACL,gBAAgB,EAChB,4BAA4B,EAC5B,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAWtB;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,EAC7B,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,WAAW,GACC;IACZ,MAAM,EAAE,UAAU,EAAE,GAAG,oBAAoB,EAAE,CAAC;IAC9C,MAAM,EAAC,QAAQ,EAAC,GAAG,WAAW,EAAE,CAAC;IACjC,MAAM,WAAW,GAAG,UAAU,CAAC,WAAwC,CAAC;IACxE,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,IAAI,EAAE,CAAC;IAE9C,2DAA2D;IAC3D,MAAM,WAAW,GAAG,QAAQ;QAC1B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC;YACE,GAAG,4BAA4B,EAAE;YACjC,GAAG,QAAQ,CAAC,MAAM;YAClB,WAAW,EAAE;gBACX,GAAG,kBAAkB;gBACrB,GAAG,QAAQ,CAAC,MAAM,EAAE,WAAW;aAChC;SACF,CAAC;IAEN,oEAAoE;IACpE,MAAM,aAAa,GAAG,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CACnD,CAAC,CAAC,CAAC,UAAU,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CACnD,CAAC;IACF,MAAM,SAAS,GAAG,aAAa;QAC7B,CAAC,CAAC,EAAC,OAAO,EAAE,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC,KAAK,EAAC;QAC9D,CAAC,CAAC,SAAS,CAAC;IAEd,OAAO,CACL,KAAC,gBAAgB,IACf,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE;YACJ,QAAQ,EAAE,UAAU,CAAC,KAAK;SAC3B,YAEA,QAAQ,GACQ,CACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComponentTypes.d.ts","sourceRoot":"","sources":["../../../theme/NavbarItem/ComponentTypes.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,uBAAuB,CAAC;;;;AAUlD,wBAGE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ProductPicker from './types/ProductPicker';
|
|
2
|
+
// @theme-original resolves to OUR OWN file in a plugin theme (Docusaurus sets
|
|
3
|
+
// both @theme and @theme-original to the plugin file). @theme-init resolves to
|
|
4
|
+
// the version from the upstream theme (theme-classic) — which is what we want.
|
|
5
|
+
// require() (not import) prevents webpack from hoisting this into the ESM init
|
|
6
|
+
// order, which would cause "__WEBPACK_DEFAULT_EXPORT__ before initialization".
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
8
|
+
const ComponentTypesOrig = require('@theme-init/NavbarItem/ComponentTypes').default;
|
|
9
|
+
export default {
|
|
10
|
+
...ComponentTypesOrig,
|
|
11
|
+
'custom-productPicker': ProductPicker,
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=ComponentTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComponentTypes.js","sourceRoot":"","sources":["../../../theme/NavbarItem/ComponentTypes.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,uBAAuB,CAAC;AAElD,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,8DAA8D;AAC9D,MAAM,kBAAkB,GAAG,OAAO,CAAC,uCAAuC,CAAC,CAAC,OAA8B,CAAC;AAE3G,eAAe;IACb,GAAG,kBAAkB;IACrB,sBAAsB,EAAE,aAAa;CACtC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx"],"names":[],"mappings":"AAgBA,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAChD,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAO,EACP,GAAG,KAAK,EACT,EAAE,GAAG,2CAsGL"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createElement as _createElement } from "react";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Swizzled DropdownNavbarItemDesktop
|
|
5
|
+
*
|
|
6
|
+
* Opens on hover. The panel stays open until the user actually moves their
|
|
7
|
+
* cursor into it — so the gap between the trigger and the fixed panel never
|
|
8
|
+
* causes a flicker. Once the cursor has entered the panel, leaving it
|
|
9
|
+
* (or clicking outside) closes it normally.
|
|
10
|
+
*/
|
|
11
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
12
|
+
import clsx from 'clsx';
|
|
13
|
+
import NavbarNavLinkOrig from '@theme/NavbarItem/NavbarNavLink';
|
|
14
|
+
import NavbarItemOrig from '@theme/NavbarItem';
|
|
15
|
+
const NavbarNavLink = NavbarNavLinkOrig;
|
|
16
|
+
const NavbarItem = NavbarItemOrig;
|
|
17
|
+
export default function DropdownNavbarItemDesktop({ items, position, className, onClick, ...props }) {
|
|
18
|
+
const dropdownRef = useRef(null);
|
|
19
|
+
const hasEnteredPanel = useRef(false);
|
|
20
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
21
|
+
// Close on click / touch outside
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const close = (e) => {
|
|
24
|
+
if (!dropdownRef.current?.contains(e.target)) {
|
|
25
|
+
setShowDropdown(false);
|
|
26
|
+
hasEnteredPanel.current = false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
document.addEventListener('mousedown', close);
|
|
30
|
+
document.addEventListener('touchstart', close);
|
|
31
|
+
return () => {
|
|
32
|
+
document.removeEventListener('mousedown', close);
|
|
33
|
+
document.removeEventListener('touchstart', close);
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
// Sync: close other open megamenus when this one opens
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const onOtherOpen = (e) => {
|
|
39
|
+
if (e.detail.label !== props.label) {
|
|
40
|
+
setShowDropdown(false);
|
|
41
|
+
hasEnteredPanel.current = false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
window.addEventListener('nf-picker:open', onOtherOpen);
|
|
45
|
+
return () => window.removeEventListener('nf-picker:open', onOtherOpen);
|
|
46
|
+
}, [props.label]);
|
|
47
|
+
// Open on hover — reset entry state each time
|
|
48
|
+
const handleMouseEnter = useCallback(() => {
|
|
49
|
+
hasEnteredPanel.current = false;
|
|
50
|
+
window.dispatchEvent(new CustomEvent('nf-picker:open', { detail: { label: props.label } }));
|
|
51
|
+
setShowDropdown(true);
|
|
52
|
+
console.log('[product-picker] popped open:', props.label);
|
|
53
|
+
}, [props.label]);
|
|
54
|
+
// Leaving the trigger: do nothing — the panel stays open until the user
|
|
55
|
+
// either enters it (then leaves) or clicks outside.
|
|
56
|
+
const handleTriggerLeave = useCallback(() => {
|
|
57
|
+
console.log('[product-picker] trigger leave — hasEnteredPanel:', hasEnteredPanel.current);
|
|
58
|
+
}, []);
|
|
59
|
+
// Once the cursor enters the panel, normal leave/blur can close it
|
|
60
|
+
const handlePanelEnter = useCallback(() => {
|
|
61
|
+
hasEnteredPanel.current = true;
|
|
62
|
+
console.log('[product-picker] panel focus obtained');
|
|
63
|
+
}, []);
|
|
64
|
+
const handlePanelLeave = useCallback(() => {
|
|
65
|
+
console.log('[product-picker] panel focus lost — hasEnteredPanel:', hasEnteredPanel.current);
|
|
66
|
+
if (hasEnteredPanel.current) {
|
|
67
|
+
setShowDropdown(false);
|
|
68
|
+
hasEnteredPanel.current = false;
|
|
69
|
+
console.log('[product-picker] closing — cursor left panel');
|
|
70
|
+
}
|
|
71
|
+
}, []);
|
|
72
|
+
return (_jsxs("div", { ref: dropdownRef, className: clsx('navbar__item', 'dropdown', {
|
|
73
|
+
'dropdown--right': position === 'right',
|
|
74
|
+
'dropdown--show': showDropdown,
|
|
75
|
+
}), onMouseEnter: handleMouseEnter, onMouseLeave: handleTriggerLeave, children: [_jsx(NavbarNavLink, { "aria-haspopup": "true", "aria-expanded": showDropdown, role: "button", href: props.to ? undefined : '#', className: clsx('navbar__link', className), ...props, onClick: props.to ? undefined : (e) => e.preventDefault(), onKeyDown: (e) => {
|
|
76
|
+
if (e.key === 'Enter') {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
setShowDropdown(prev => !prev);
|
|
79
|
+
}
|
|
80
|
+
}, children: props.children ?? props.label }), _jsx("ul", { className: "dropdown__menu", onMouseEnter: handlePanelEnter, onMouseLeave: handlePanelLeave, children: items.map((childItemProps, i) => (_createElement(NavbarItem, { isDropdownItem: true, activeClassName: "dropdown__link--active", ...childItemProps, key: i }))) })] }));
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type PickerLink = {
|
|
2
|
+
label: string;
|
|
3
|
+
to: string;
|
|
4
|
+
logo?: string;
|
|
5
|
+
logoDark?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
};
|
|
8
|
+
export type PickerColumn = {
|
|
9
|
+
header: string;
|
|
10
|
+
headerClass?: string;
|
|
11
|
+
links: PickerLink[];
|
|
12
|
+
};
|
|
13
|
+
type Props = {
|
|
14
|
+
label?: string;
|
|
15
|
+
position?: 'left' | 'right';
|
|
16
|
+
className?: string;
|
|
17
|
+
};
|
|
18
|
+
export default function ProductPicker({ label, className }: Props): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../theme/NavbarItem/types/ProductPicker/index.tsx"],"names":[],"mappings":"AAMA,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAgCF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAC,KAAkB,EAAE,SAAS,EAAC,EAAE,KAAK,2CAmG3E"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import Link from '@docusaurus/Link';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
6
|
+
import { useThemeConfig } from '@docusaurus/theme-common';
|
|
7
|
+
const HEADER_CLASSES = ['picker-header--nf-primary', 'picker-header--nf-secondary', 'picker-header--nf-tertiary'];
|
|
8
|
+
const NF_LOGO_DEFAULT = 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg';
|
|
9
|
+
const buildDefaultColumns = (img, consoleLogo) => [
|
|
10
|
+
{
|
|
11
|
+
header: 'Managed Cloud',
|
|
12
|
+
headerClass: HEADER_CLASSES[0],
|
|
13
|
+
links: [
|
|
14
|
+
{ label: 'NetFoundry Console', to: '#', logo: consoleLogo, description: 'Cloud-managed orchestration and global fabric control.' },
|
|
15
|
+
{ label: 'Frontdoor', to: '/docs/frontdoor', logo: `${img}/frontdoor-sm-logo.svg`, description: 'Secure application access gateway.' },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
header: 'Open Source',
|
|
20
|
+
headerClass: HEADER_CLASSES[1],
|
|
21
|
+
links: [
|
|
22
|
+
{ label: 'OpenZiti', to: '/docs/openziti', logo: `${img}/openziti-sm-logo.svg`, description: 'Programmable zero-trust mesh infrastructure.' },
|
|
23
|
+
{ label: 'zrok', to: '/docs/zrok', logo: `${img}/zrok-1.0.0-rocket-purple.svg`, logoDark: `${img}/zrok-1.0.0-rocket-green.svg`, description: 'Secure peer-to-peer sharing built on OpenZiti.' },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
header: 'Your own infrastructure',
|
|
28
|
+
headerClass: HEADER_CLASSES[2],
|
|
29
|
+
links: [
|
|
30
|
+
{ label: 'Self-Hosted', to: '/docs/selfhosted', logo: `${img}/onprem-sm-logo.svg`, description: 'Deploy the full stack in your own environment.' },
|
|
31
|
+
{ label: 'zLAN', to: '/docs/zlan', logo: `${img}/zlan-logo.svg`, description: 'Zero-trust access for OT networks.' },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
export default function ProductPicker({ label = 'Products', className }) {
|
|
36
|
+
const { siteConfig } = useDocusaurusContext();
|
|
37
|
+
const themeConfig = useThemeConfig();
|
|
38
|
+
const consoleLogo = themeConfig?.netfoundry?.consoleLogo ?? NF_LOGO_DEFAULT;
|
|
39
|
+
const img = `${siteConfig.url}${siteConfig.baseUrl}img`;
|
|
40
|
+
const columns = (themeConfig?.netfoundry?.productPickerColumns ?? [])
|
|
41
|
+
.map((col, i) => ({ ...col, headerClass: HEADER_CLASSES[i] ?? '' }));
|
|
42
|
+
const resolvedColumns = columns.length ? columns : buildDefaultColumns(img, consoleLogo);
|
|
43
|
+
const wrapRef = useRef(null);
|
|
44
|
+
const hasEnteredPanel = useRef(false);
|
|
45
|
+
const [open, setOpen] = useState(false);
|
|
46
|
+
const close = useCallback(() => {
|
|
47
|
+
setOpen(false);
|
|
48
|
+
hasEnteredPanel.current = false;
|
|
49
|
+
}, []);
|
|
50
|
+
// Close on outside click/touch
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const onOutside = (e) => {
|
|
53
|
+
if (!wrapRef.current?.contains(e.target))
|
|
54
|
+
close();
|
|
55
|
+
};
|
|
56
|
+
document.addEventListener('mousedown', onOutside);
|
|
57
|
+
document.addEventListener('touchstart', onOutside);
|
|
58
|
+
return () => {
|
|
59
|
+
document.removeEventListener('mousedown', onOutside);
|
|
60
|
+
document.removeEventListener('touchstart', onOutside);
|
|
61
|
+
};
|
|
62
|
+
}, [close]);
|
|
63
|
+
// Sync: close when another product picker opens
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const onOtherOpen = (e) => {
|
|
66
|
+
if (e.detail.label !== label)
|
|
67
|
+
close();
|
|
68
|
+
};
|
|
69
|
+
window.addEventListener('nf-picker:open', onOtherOpen);
|
|
70
|
+
return () => window.removeEventListener('nf-picker:open', onOtherOpen);
|
|
71
|
+
}, [label, close]);
|
|
72
|
+
const handleTriggerEnter = useCallback(() => {
|
|
73
|
+
hasEnteredPanel.current = false;
|
|
74
|
+
window.dispatchEvent(new CustomEvent('nf-picker:open', { detail: { label } }));
|
|
75
|
+
setOpen(true);
|
|
76
|
+
}, [label]);
|
|
77
|
+
// Stay open until user enters the panel — no timer
|
|
78
|
+
const handleTriggerLeave = useCallback(() => { }, []);
|
|
79
|
+
const handlePanelEnter = useCallback(() => {
|
|
80
|
+
hasEnteredPanel.current = true;
|
|
81
|
+
}, []);
|
|
82
|
+
const handlePanelLeave = useCallback(() => {
|
|
83
|
+
if (hasEnteredPanel.current)
|
|
84
|
+
close();
|
|
85
|
+
}, [close]);
|
|
86
|
+
return (_jsxs("div", { ref: wrapRef, className: clsx('navbar__item', { 'nf-picker--open': open }), children: [_jsx("a", { role: "button", href: "#", "aria-haspopup": "true", "aria-expanded": open, className: clsx('navbar__link', 'nf-picker-trigger', className), onMouseEnter: handleTriggerEnter, onMouseLeave: handleTriggerLeave, onClick: e => { e.preventDefault(); setOpen(o => !o); }, onKeyDown: e => { if (e.key === 'Enter') {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
setOpen(o => !o);
|
|
89
|
+
} }, children: label }), open && (_jsx("div", { className: "nf-picker-panel", onMouseDown: e => e.stopPropagation(), onMouseEnter: handlePanelEnter, onMouseLeave: handlePanelLeave, children: _jsx("div", { className: "picker-content", children: resolvedColumns.map((col, i) => (_jsxs("div", { className: "picker-column", children: [_jsx("span", { className: clsx('picker-header', col.headerClass), children: col.header }), col.links.map((link, j) => (_jsxs(Link, { to: link.to, className: "picker-link", children: [link.logo && _jsx("img", { src: link.logo, className: clsx('picker-logo', link.logoDark && 'picker-logo--light'), alt: "" }), link.logoDark && _jsx("img", { src: link.logoDark, className: "picker-logo picker-logo--dark", alt: "" }), _jsxs("div", { className: "picker-text", children: [_jsx("strong", { children: link.label }), link.description && _jsx("span", { children: link.description })] })] }, j)))] }, i))) }) }))] }));
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../theme/NavbarItem/types/ProductPicker/index.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netfoundry/docusaurus-theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "NetFoundry Docusaurus theme with shared layout, footer, and styling",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"clean": "node scripts/clean.mjs",
|
|
14
14
|
"build": "yarn clean && tsc && yarn build:css",
|
|
15
|
+
"watch": "tsc --watch",
|
|
15
16
|
"build:css": "node scripts/build-css.mjs",
|
|
16
17
|
"test": "jest",
|
|
17
18
|
"prepublishOnly": "yarn build && yarn test"
|
|
@@ -36,7 +37,11 @@
|
|
|
36
37
|
"types": "./dist/src/node.d.ts",
|
|
37
38
|
"default": "./dist/src/node.js"
|
|
38
39
|
},
|
|
39
|
-
"./css/*": "./css/*"
|
|
40
|
+
"./css/*": "./css/*",
|
|
41
|
+
"./theme/NavbarItem/types/ProductPicker": {
|
|
42
|
+
"types": "./theme/NavbarItem/types/ProductPicker/index.tsx",
|
|
43
|
+
"default": "./theme/NavbarItem/types/ProductPicker/index.tsx"
|
|
44
|
+
}
|
|
40
45
|
},
|
|
41
46
|
"peerDependencies": {
|
|
42
47
|
"@docusaurus/core": "^3",
|
package/theme/Layout/index.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { type ReactNode } from 'react';
|
|
2
2
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
3
|
+
import {useLocation} from 'react-router-dom';
|
|
3
4
|
import {
|
|
4
5
|
NetFoundryLayout,
|
|
5
6
|
defaultNetFoundryFooterProps,
|
|
@@ -32,6 +33,7 @@ export default function Layout({
|
|
|
32
33
|
description,
|
|
33
34
|
}: LayoutProps): ReactNode {
|
|
34
35
|
const { siteConfig } = useDocusaurusContext();
|
|
36
|
+
const {pathname} = useLocation();
|
|
35
37
|
const themeConfig = siteConfig.themeConfig as ThemeConfigWithNetFoundry;
|
|
36
38
|
const nfConfig = themeConfig.netfoundry ?? {};
|
|
37
39
|
|
|
@@ -47,14 +49,13 @@ export default function Layout({
|
|
|
47
49
|
},
|
|
48
50
|
};
|
|
49
51
|
|
|
50
|
-
//
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
: undefined;
|
|
52
|
+
// Pick the first banner whose pathPrefix matches (or has no prefix)
|
|
53
|
+
const matchedBanner = nfConfig.starBanners?.find(b =>
|
|
54
|
+
!b.pathPrefix || pathname.startsWith(b.pathPrefix)
|
|
55
|
+
);
|
|
56
|
+
const starProps = matchedBanner
|
|
57
|
+
? {repoUrl: matchedBanner.repoUrl, label: matchedBanner.label}
|
|
58
|
+
: undefined;
|
|
58
59
|
|
|
59
60
|
return (
|
|
60
61
|
<NetFoundryLayout
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ProductPicker from './types/ProductPicker';
|
|
2
|
+
|
|
3
|
+
// @theme-original resolves to OUR OWN file in a plugin theme (Docusaurus sets
|
|
4
|
+
// both @theme and @theme-original to the plugin file). @theme-init resolves to
|
|
5
|
+
// the version from the upstream theme (theme-classic) — which is what we want.
|
|
6
|
+
// require() (not import) prevents webpack from hoisting this into the ESM init
|
|
7
|
+
// order, which would cause "__WEBPACK_DEFAULT_EXPORT__ before initialization".
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
9
|
+
const ComponentTypesOrig = require('@theme-init/NavbarItem/ComponentTypes').default as Record<string, any>;
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
...ComponentTypesOrig,
|
|
13
|
+
'custom-productPicker': ProductPicker,
|
|
14
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swizzled DropdownNavbarItemDesktop
|
|
3
|
+
*
|
|
4
|
+
* Opens on hover. The panel stays open until the user actually moves their
|
|
5
|
+
* cursor into it — so the gap between the trigger and the fixed panel never
|
|
6
|
+
* causes a flicker. Once the cursor has entered the panel, leaving it
|
|
7
|
+
* (or clicking outside) closes it normally.
|
|
8
|
+
*/
|
|
9
|
+
import React, {useState, useRef, useEffect, useCallback} from 'react';
|
|
10
|
+
import clsx from 'clsx';
|
|
11
|
+
import NavbarNavLinkOrig from '@theme/NavbarItem/NavbarNavLink';
|
|
12
|
+
import NavbarItemOrig from '@theme/NavbarItem';
|
|
13
|
+
|
|
14
|
+
const NavbarNavLink = NavbarNavLinkOrig as React.ComponentType<any>;
|
|
15
|
+
const NavbarItem = NavbarItemOrig as React.ComponentType<any>;
|
|
16
|
+
|
|
17
|
+
export default function DropdownNavbarItemDesktop({
|
|
18
|
+
items,
|
|
19
|
+
position,
|
|
20
|
+
className,
|
|
21
|
+
onClick,
|
|
22
|
+
...props
|
|
23
|
+
}: any) {
|
|
24
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
const hasEnteredPanel = useRef(false);
|
|
26
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
27
|
+
|
|
28
|
+
// Close on click / touch outside
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const close = (e: MouseEvent | TouchEvent) => {
|
|
31
|
+
if (!dropdownRef.current?.contains(e.target as Node)) {
|
|
32
|
+
setShowDropdown(false);
|
|
33
|
+
hasEnteredPanel.current = false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
document.addEventListener('mousedown', close);
|
|
37
|
+
document.addEventListener('touchstart', close);
|
|
38
|
+
return () => {
|
|
39
|
+
document.removeEventListener('mousedown', close);
|
|
40
|
+
document.removeEventListener('touchstart', close);
|
|
41
|
+
};
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
// Sync: close other open megamenus when this one opens
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const onOtherOpen = (e: any) => {
|
|
47
|
+
if (e.detail.label !== props.label) {
|
|
48
|
+
setShowDropdown(false);
|
|
49
|
+
hasEnteredPanel.current = false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
window.addEventListener('nf-picker:open', onOtherOpen);
|
|
53
|
+
return () => window.removeEventListener('nf-picker:open', onOtherOpen);
|
|
54
|
+
}, [props.label]);
|
|
55
|
+
|
|
56
|
+
// Open on hover — reset entry state each time
|
|
57
|
+
const handleMouseEnter = useCallback(() => {
|
|
58
|
+
hasEnteredPanel.current = false;
|
|
59
|
+
window.dispatchEvent(new CustomEvent('nf-picker:open', {detail: {label: props.label}}));
|
|
60
|
+
setShowDropdown(true);
|
|
61
|
+
console.log('[product-picker] popped open:', props.label);
|
|
62
|
+
}, [props.label]);
|
|
63
|
+
|
|
64
|
+
// Leaving the trigger: do nothing — the panel stays open until the user
|
|
65
|
+
// either enters it (then leaves) or clicks outside.
|
|
66
|
+
const handleTriggerLeave = useCallback(() => {
|
|
67
|
+
console.log('[product-picker] trigger leave — hasEnteredPanel:', hasEnteredPanel.current);
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
// Once the cursor enters the panel, normal leave/blur can close it
|
|
71
|
+
const handlePanelEnter = useCallback(() => {
|
|
72
|
+
hasEnteredPanel.current = true;
|
|
73
|
+
console.log('[product-picker] panel focus obtained');
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
const handlePanelLeave = useCallback(() => {
|
|
77
|
+
console.log('[product-picker] panel focus lost — hasEnteredPanel:', hasEnteredPanel.current);
|
|
78
|
+
if (hasEnteredPanel.current) {
|
|
79
|
+
setShowDropdown(false);
|
|
80
|
+
hasEnteredPanel.current = false;
|
|
81
|
+
console.log('[product-picker] closing — cursor left panel');
|
|
82
|
+
}
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
ref={dropdownRef}
|
|
88
|
+
className={clsx('navbar__item', 'dropdown', {
|
|
89
|
+
'dropdown--right': position === 'right',
|
|
90
|
+
'dropdown--show': showDropdown,
|
|
91
|
+
})}
|
|
92
|
+
onMouseEnter={handleMouseEnter}
|
|
93
|
+
onMouseLeave={handleTriggerLeave}>
|
|
94
|
+
<NavbarNavLink
|
|
95
|
+
aria-haspopup="true"
|
|
96
|
+
aria-expanded={showDropdown}
|
|
97
|
+
role="button"
|
|
98
|
+
href={props.to ? undefined : '#'}
|
|
99
|
+
className={clsx('navbar__link', className)}
|
|
100
|
+
{...props}
|
|
101
|
+
onClick={props.to ? undefined : (e: React.MouseEvent) => e.preventDefault()}
|
|
102
|
+
onKeyDown={(e: React.KeyboardEvent) => {
|
|
103
|
+
if (e.key === 'Enter') {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
setShowDropdown(prev => !prev);
|
|
106
|
+
}
|
|
107
|
+
}}>
|
|
108
|
+
{props.children ?? props.label}
|
|
109
|
+
</NavbarNavLink>
|
|
110
|
+
<ul
|
|
111
|
+
className="dropdown__menu"
|
|
112
|
+
onMouseEnter={handlePanelEnter}
|
|
113
|
+
onMouseLeave={handlePanelLeave}>
|
|
114
|
+
{items.map((childItemProps: any, i: number) => (
|
|
115
|
+
<NavbarItem
|
|
116
|
+
isDropdownItem
|
|
117
|
+
activeClassName="dropdown__link--active"
|
|
118
|
+
{...childItemProps}
|
|
119
|
+
key={i}
|
|
120
|
+
/>
|
|
121
|
+
))}
|
|
122
|
+
</ul>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React, {useState, useRef, useEffect, useCallback} from 'react';
|
|
2
|
+
import Link from '@docusaurus/Link';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
5
|
+
import {useThemeConfig} from '@docusaurus/theme-common';
|
|
6
|
+
|
|
7
|
+
export type PickerLink = {
|
|
8
|
+
label: string;
|
|
9
|
+
to: string;
|
|
10
|
+
logo?: string;
|
|
11
|
+
logoDark?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type PickerColumn = {
|
|
16
|
+
header: string;
|
|
17
|
+
headerClass?: string;
|
|
18
|
+
links: PickerLink[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type Props = {
|
|
22
|
+
label?: string;
|
|
23
|
+
position?: 'left' | 'right';
|
|
24
|
+
className?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const HEADER_CLASSES = ['picker-header--nf-primary', 'picker-header--nf-secondary', 'picker-header--nf-tertiary'];
|
|
28
|
+
const NF_LOGO_DEFAULT = 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg';
|
|
29
|
+
|
|
30
|
+
const buildDefaultColumns = (img: string, consoleLogo: string): PickerColumn[] => [
|
|
31
|
+
{
|
|
32
|
+
header: 'Managed Cloud',
|
|
33
|
+
headerClass: HEADER_CLASSES[0],
|
|
34
|
+
links: [
|
|
35
|
+
{ label: 'NetFoundry Console', to: '#', logo: consoleLogo, description: 'Cloud-managed orchestration and global fabric control.' },
|
|
36
|
+
{ label: 'Frontdoor', to: '/docs/frontdoor', logo: `${img}/frontdoor-sm-logo.svg`, description: 'Secure application access gateway.' },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
header: 'Open Source',
|
|
41
|
+
headerClass: HEADER_CLASSES[1],
|
|
42
|
+
links: [
|
|
43
|
+
{ label: 'OpenZiti', to: '/docs/openziti', logo: `${img}/openziti-sm-logo.svg`, description: 'Programmable zero-trust mesh infrastructure.' },
|
|
44
|
+
{ label: 'zrok', to: '/docs/zrok', logo: `${img}/zrok-1.0.0-rocket-purple.svg`, logoDark: `${img}/zrok-1.0.0-rocket-green.svg`, description: 'Secure peer-to-peer sharing built on OpenZiti.' },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
header: 'Your own infrastructure',
|
|
49
|
+
headerClass: HEADER_CLASSES[2],
|
|
50
|
+
links: [
|
|
51
|
+
{ label: 'Self-Hosted', to: '/docs/selfhosted', logo: `${img}/onprem-sm-logo.svg`, description: 'Deploy the full stack in your own environment.' },
|
|
52
|
+
{ label: 'zLAN', to: '/docs/zlan', logo: `${img}/zlan-logo.svg`, description: 'Zero-trust access for OT networks.' },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export default function ProductPicker({label = 'Products', className}: Props) {
|
|
58
|
+
const {siteConfig} = useDocusaurusContext();
|
|
59
|
+
const themeConfig = useThemeConfig() as any;
|
|
60
|
+
const consoleLogo = themeConfig?.netfoundry?.consoleLogo ?? NF_LOGO_DEFAULT;
|
|
61
|
+
const img = `${siteConfig.url}${siteConfig.baseUrl}img`;
|
|
62
|
+
const columns: PickerColumn[] = (themeConfig?.netfoundry?.productPickerColumns ?? [])
|
|
63
|
+
.map((col: any, i: number) => ({...col, headerClass: HEADER_CLASSES[i] ?? ''}));
|
|
64
|
+
const resolvedColumns = columns.length ? columns : buildDefaultColumns(img, consoleLogo);
|
|
65
|
+
const wrapRef = useRef<HTMLDivElement>(null);
|
|
66
|
+
const hasEnteredPanel = useRef(false);
|
|
67
|
+
const [open, setOpen] = useState(false);
|
|
68
|
+
|
|
69
|
+
const close = useCallback(() => {
|
|
70
|
+
setOpen(false);
|
|
71
|
+
hasEnteredPanel.current = false;
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
// Close on outside click/touch
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const onOutside = (e: MouseEvent | TouchEvent) => {
|
|
77
|
+
if (!wrapRef.current?.contains(e.target as Node)) close();
|
|
78
|
+
};
|
|
79
|
+
document.addEventListener('mousedown', onOutside);
|
|
80
|
+
document.addEventListener('touchstart', onOutside);
|
|
81
|
+
return () => {
|
|
82
|
+
document.removeEventListener('mousedown', onOutside);
|
|
83
|
+
document.removeEventListener('touchstart', onOutside);
|
|
84
|
+
};
|
|
85
|
+
}, [close]);
|
|
86
|
+
|
|
87
|
+
// Sync: close when another product picker opens
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const onOtherOpen = (e: any) => {
|
|
90
|
+
if (e.detail.label !== label) close();
|
|
91
|
+
};
|
|
92
|
+
window.addEventListener('nf-picker:open', onOtherOpen);
|
|
93
|
+
return () => window.removeEventListener('nf-picker:open', onOtherOpen);
|
|
94
|
+
}, [label, close]);
|
|
95
|
+
|
|
96
|
+
const handleTriggerEnter = useCallback(() => {
|
|
97
|
+
hasEnteredPanel.current = false;
|
|
98
|
+
window.dispatchEvent(new CustomEvent('nf-picker:open', {detail: {label}}));
|
|
99
|
+
setOpen(true);
|
|
100
|
+
}, [label]);
|
|
101
|
+
|
|
102
|
+
// Stay open until user enters the panel — no timer
|
|
103
|
+
const handleTriggerLeave = useCallback(() => {}, []);
|
|
104
|
+
|
|
105
|
+
const handlePanelEnter = useCallback(() => {
|
|
106
|
+
hasEnteredPanel.current = true;
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
const handlePanelLeave = useCallback(() => {
|
|
110
|
+
if (hasEnteredPanel.current) close();
|
|
111
|
+
}, [close]);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
ref={wrapRef}
|
|
116
|
+
className={clsx('navbar__item', {'nf-picker--open': open})}>
|
|
117
|
+
<a
|
|
118
|
+
role="button"
|
|
119
|
+
href="#"
|
|
120
|
+
aria-haspopup="true"
|
|
121
|
+
aria-expanded={open}
|
|
122
|
+
className={clsx('navbar__link', 'nf-picker-trigger', className)}
|
|
123
|
+
onMouseEnter={handleTriggerEnter}
|
|
124
|
+
onMouseLeave={handleTriggerLeave}
|
|
125
|
+
onClick={e => { e.preventDefault(); setOpen(o => !o); }}
|
|
126
|
+
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); setOpen(o => !o); } }}>
|
|
127
|
+
{label}
|
|
128
|
+
</a>
|
|
129
|
+
{open && (
|
|
130
|
+
<div
|
|
131
|
+
className="nf-picker-panel"
|
|
132
|
+
onMouseDown={e => e.stopPropagation()}
|
|
133
|
+
onMouseEnter={handlePanelEnter}
|
|
134
|
+
onMouseLeave={handlePanelLeave}>
|
|
135
|
+
<div className="picker-content">
|
|
136
|
+
{resolvedColumns.map((col, i) => (
|
|
137
|
+
<div key={i} className="picker-column">
|
|
138
|
+
<span className={clsx('picker-header', col.headerClass)}>{col.header}</span>
|
|
139
|
+
{col.links.map((link, j) => (
|
|
140
|
+
<Link key={j} to={link.to} className="picker-link">
|
|
141
|
+
{link.logo && <img src={link.logo} className={clsx('picker-logo', link.logoDark && 'picker-logo--light')} alt="" />}
|
|
142
|
+
{link.logoDark && <img src={link.logoDark} className="picker-logo picker-logo--dark" alt="" />}
|
|
143
|
+
<div className="picker-text">
|
|
144
|
+
<strong>{link.label}</strong>
|
|
145
|
+
{link.description && <span>{link.description}</span>}
|
|
146
|
+
</div>
|
|
147
|
+
</Link>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type stubs for Docusaurus @theme/* virtual modules.
|
|
3
|
+
* These are resolved at runtime by Docusaurus's webpack aliases and are not
|
|
4
|
+
* available to the standalone TypeScript compiler. Declared as `any`-based
|
|
5
|
+
* components here since all call sites cast them to ComponentType<any> anyway.
|
|
6
|
+
*/
|
|
7
|
+
declare module '@docusaurus/Link' {
|
|
8
|
+
const Link: any;
|
|
9
|
+
export default Link;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module '@theme-original/NavbarItem/ComponentTypes' {
|
|
13
|
+
const ComponentTypes: any;
|
|
14
|
+
export default ComponentTypes;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare module '@theme-init/NavbarItem/ComponentTypes' {
|
|
18
|
+
const ComponentTypes: any;
|
|
19
|
+
export default ComponentTypes;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module '@theme/NavbarItem/NavbarNavLink' {
|
|
23
|
+
import type React from 'react';
|
|
24
|
+
const NavbarNavLink: React.ComponentType<any>;
|
|
25
|
+
export default NavbarNavLink;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare module '@theme/NavbarItem' {
|
|
29
|
+
import type React from 'react';
|
|
30
|
+
const NavbarItem: React.ComponentType<any>;
|
|
31
|
+
export default NavbarItem;
|
|
32
|
+
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"WebFetch(domain:netfoundry.io)",
|
|
5
|
-
"WebSearch",
|
|
6
|
-
"WebFetch(domain:github.com)",
|
|
7
|
-
"WebFetch(domain:colineberhardt.github.io)",
|
|
8
|
-
"WebFetch(domain:cla-assistant.io)",
|
|
9
|
-
"WebFetch(domain:raw.githubusercontent.com)"
|
|
10
|
-
]
|
|
11
|
-
}
|
|
12
|
-
}
|