Skip to content

Commit 0461dbe

Browse files
committed
feat: Add dynamic Learn More button and reorder navbar
- Create custom ProductLearnMoreLink navbar component - Add UTM tracking parameters to purchase URLs - Reorder menus: GravityKit Products before GravityView - Style navbar button with proper spacing
1 parent 1618541 commit 0461dbe

4 files changed

Lines changed: 227 additions & 10 deletions

File tree

docusaurus.config.js

Lines changed: 101 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,103 @@ const llms_sitemap_paths = [
4141
...products_with_docs.map((product) => `docs/${product.id}/llms.txt`),
4242
];
4343

44-
// Generate navigation items from products
45-
const product_nav_items = config_products
46-
.filter((product) => product?.label && product?.id)
47-
.map((product) => ({
48-
label: product.label,
49-
href: `/docs/${product.id}/`,
50-
}));
44+
// Generate navigation items grouped by category
45+
const categories = repos_config.categories || {};
46+
47+
// Helper to get products by category
48+
function getProductsByCategory(categoryId) {
49+
return config_products
50+
.filter((p) => p?.category === categoryId && p?.label && p?.id)
51+
.map((p) => ({
52+
label: p.label,
53+
href: `/docs/${p.id}/`,
54+
}));
55+
}
56+
57+
// Helper to get free add-ons (products with isFree: true)
58+
function getFreeProducts() {
59+
return config_products
60+
.filter((p) => p?.isFree === true && p?.label && p?.id)
61+
.map((p) => ({
62+
label: p.label,
63+
href: `/docs/${p.id}/`,
64+
}));
65+
}
66+
67+
// Helper to get third-party products (products with isThirdParty: true)
68+
function getThirdPartyProducts() {
69+
return config_products
70+
.filter((p) => p?.isThirdParty === true && p?.label && p?.id)
71+
.map((p) => ({
72+
label: p.label,
73+
href: `/docs/${p.id}/`,
74+
}));
75+
}
76+
77+
// Build GravityView dropdown with nested extensions and layouts
78+
const gravityview_nav = {
79+
label: 'GravityView',
80+
position: 'left',
81+
items: [
82+
{ label: 'GravityView', href: '/docs/gravityview/' },
83+
{
84+
type: 'html',
85+
value: '<hr class="dropdown-separator">',
86+
},
87+
{
88+
type: 'html',
89+
value: '<span class="dropdown-heading">Extensions</span>',
90+
className: 'dropdown-heading-item',
91+
},
92+
...getProductsByCategory('gravityview-extensions'),
93+
{
94+
type: 'html',
95+
value: '<hr class="dropdown-separator">',
96+
},
97+
{
98+
type: 'html',
99+
value: '<span class="dropdown-heading">Layouts</span>',
100+
className: 'dropdown-heading-item',
101+
},
102+
...getProductsByCategory('gravityview-layouts'),
103+
],
104+
};
105+
106+
// Build GravityKit Products dropdown (includes GravityView, free add-ons, and third-party)
107+
const gravitykit_nav = {
108+
label: 'GravityKit Products',
109+
position: 'left',
110+
items: [
111+
...getProductsByCategory('gravitykit'),
112+
{ label: 'GravityView', href: '/docs/gravityview/' },
113+
{
114+
type: 'html',
115+
value: '<hr class="dropdown-separator">',
116+
},
117+
{
118+
type: 'html',
119+
value: '<span class="dropdown-heading">Free Add-Ons</span>',
120+
className: 'dropdown-heading-item',
121+
},
122+
...getFreeProducts(),
123+
{
124+
type: 'html',
125+
value: '<hr class="dropdown-separator">',
126+
},
127+
{
128+
type: 'html',
129+
value: '<span class="dropdown-heading">Third Party</span>',
130+
className: 'dropdown-heading-item',
131+
},
132+
...getThirdPartyProducts(),
133+
],
134+
};
135+
136+
// Helper to get purchase URL for a product from repos-config.json
137+
function getProductPurchaseUrl(productId) {
138+
const product = config_products.find((p) => p.id === productId);
139+
return product?.purchaseUrl || null;
140+
}
51141

52142
// Generate docs plugins for each product
53143
// Documentation is generated to ./docs/{product-id}/
@@ -273,10 +363,11 @@ const config = {
273363
src: 'img/gravitykit-icon.svg',
274364
},
275365
items: [
366+
gravitykit_nav,
367+
gravityview_nav,
276368
{
277-
label: 'Products',
278-
position: 'left',
279-
items: product_nav_items,
369+
type: 'custom-productLearnMoreLink',
370+
position: 'right',
280371
},
281372
],
282373
},

src/css/custom.css

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,51 @@
206206
.hook-params dd {
207207
margin-left: 2rem;
208208
margin-bottom: 0.5rem;
209+
}
210+
211+
/* Navbar dropdown styling */
212+
.dropdown-separator {
213+
margin: 0.5rem 0;
214+
border: none;
215+
border-top: 1px solid var(--ifm-color-emphasis-200);
216+
}
217+
218+
.dropdown-heading {
219+
font-size: 0.75rem;
220+
font-weight: 600;
221+
text-transform: uppercase;
222+
letter-spacing: 0.05em;
223+
color: var(--ifm-color-emphasis-600);
224+
padding: 0.25rem 0.75rem;
225+
display: block;
226+
}
227+
228+
.dropdown-heading-item {
229+
pointer-events: none;
230+
}
231+
232+
/* Purchase/Learn More button styling */
233+
.navbar-purchase-button {
234+
background-color: var(--gk-secondary);
235+
color: white !important;
236+
padding: 0.4rem 1rem;
237+
border-radius: 0.375rem;
238+
font-weight: 600;
239+
transition: background-color 0.2s ease;
240+
margin-right: 1rem;
241+
white-space: nowrap;
242+
}
243+
244+
.navbar-purchase-button:hover {
245+
background-color: #b81248;
246+
color: white !important;
247+
text-decoration: none;
248+
}
249+
250+
[data-theme='dark'] .navbar-purchase-button {
251+
background-color: var(--gk-secondary);
252+
}
253+
254+
[data-theme='dark'] .navbar-purchase-button:hover {
255+
background-color: #e01860;
209256
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import ComponentTypes from '@theme-original/NavbarItem/ComponentTypes';
2+
import ProductLearnMoreLink from './ProductLearnMoreLink';
3+
4+
export default {
5+
...ComponentTypes,
6+
'custom-productLearnMoreLink': ProductLearnMoreLink,
7+
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from 'react';
2+
import {useLocation} from '@docusaurus/router';
3+
4+
// Import centralized product configuration
5+
import reposConfig from '../../../repos-config.json';
6+
7+
// Build product lookup map from repos-config.json
8+
const productConfig = Object.fromEntries(
9+
reposConfig.products.map((p) => [
10+
p.id,
11+
{ label: p.label, purchaseUrl: p.purchaseUrl }
12+
])
13+
);
14+
15+
// Default fallback
16+
const defaultLink = {
17+
label: 'Learn More',
18+
href: 'https://www.gravitykit.com/products/',
19+
};
20+
21+
// Add UTM parameters to URL
22+
function addUtmParams(url, productId) {
23+
const utmParams = new URLSearchParams({
24+
utm_source: 'developer-docs',
25+
utm_medium: 'navbar',
26+
utm_campaign: 'learn-more',
27+
utm_content: productId || 'general',
28+
});
29+
const separator = url.includes('?') ? '&' : '?';
30+
return `${url}${separator}${utmParams.toString()}`;
31+
}
32+
33+
export default function ProductLearnMoreLink() {
34+
const location = useLocation();
35+
36+
// Extract product ID from path: /docs/{product-id}/...
37+
const pathMatch = location.pathname.match(/^\/docs\/([^\/]+)/);
38+
const productId = pathMatch ? pathMatch[1] : null;
39+
40+
// Get product config or use default
41+
const product = productId && productConfig[productId];
42+
43+
const linkText = product ? `Learn more about ${product.label}` : defaultLink.label;
44+
const baseHref = product ? product.purchaseUrl : defaultLink.href;
45+
const linkHref = addUtmParams(baseHref, productId);
46+
47+
return (
48+
<a
49+
href={linkHref}
50+
target="_blank"
51+
rel="noopener noreferrer"
52+
className="navbar__item navbar__link navbar-purchase-button"
53+
>
54+
{linkText}
55+
<svg
56+
width="12"
57+
height="12"
58+
viewBox="0 0 24 24"
59+
fill="none"
60+
stroke="currentColor"
61+
strokeWidth="2"
62+
strokeLinecap="round"
63+
strokeLinejoin="round"
64+
style={{ marginLeft: '0.35rem', verticalAlign: 'middle' }}
65+
aria-hidden="true"
66+
>
67+
<path d="M7 17L17 7" />
68+
<path d="M7 7h10v10" />
69+
</svg>
70+
</a>
71+
);
72+
}

0 commit comments

Comments
 (0)