Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 13 additions & 39 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type * as Preset from '@docusaurus/preset-classic';
import type { Config } from '@docusaurus/types';

import clientModules from './src/plugins/client-modules.ts';
import codeSplitVideoCss from './src/plugins/code-split-video-css.ts';
import disableFullySpecified from './src/plugins/disable-fully-specified.ts';
import googleAnalyticsDeferred from './src/plugins/google-analytics-deferred.ts';
import latestAnnouncement from './src/plugins/latest-announcement.ts';
import llmsTxt from './src/plugins/llms-txt.ts';
import ogImage from './src/plugins/og-image.ts';
import reactNavigationVersions from './src/plugins/react-navigation-versions.ts';
import rehypeCodeblockMeta from './src/plugins/rehype-codeblock-meta.ts';
import rehypeVideoAspectRatio from './src/plugins/rehype-video-aspect-ratio.ts';
import rehypeMediaAttributes from './src/plugins/rehype-media-attributes.ts';
import remarkNpm2Yarn from './src/plugins/remark-npm2yarn.ts';
import remarkStaticToDynamic from './src/plugins/remark-static-to-dynamic.ts';
import darkTheme from './src/themes/react-navigation-dark';
Expand All @@ -20,7 +23,7 @@ const config: Config = {
tagline: 'Routing and navigation for your React Native apps',
url: process.env.URL || 'https://reactnavigation.org',
baseUrl: '/',
favicon: 'img/favicon.ico',
favicon: 'img/spiro.svg',
organizationName: 'react-navigation',
projectName: 'react-navigation.github.io',
onBrokenLinks: 'throw',
Expand All @@ -44,17 +47,7 @@ const config: Config = {
prism: {
theme: lightTheme,
darkTheme: darkTheme,
additionalLanguages: [
'bash',
'json',
'java',
'kotlin',
'groovy',
'ruby',
'swift',
'objectivec',
'toml',
],
additionalLanguages: ['bash', 'java', 'toml'],
magicComments: [
{
className: 'theme-code-block-highlighted-line',
Expand All @@ -75,6 +68,8 @@ const config: Config = {
logo: {
alt: 'React Navigation Logo',
src: 'img/spiro.svg',
width: 294,
height: 300,
},
items: [
{
Expand Down Expand Up @@ -139,6 +134,9 @@ const config: Config = {
},
} satisfies Preset.ThemeConfig,
plugins: [
codeSplitVideoCss,
clientModules,
googleAnalyticsDeferred,
disableFullySpecified,
reactNavigationVersions,
[llmsTxt, { latestVersion }],
Expand Down Expand Up @@ -189,7 +187,7 @@ const config: Config = {
rehypeCodeblockMeta,
{ match: { snack: true, lang: true, tabs: true } },
],
[rehypeVideoAspectRatio, { staticDir: 'static' }],
[rehypeMediaAttributes, { staticDir: 'static' }],
],
},
blog: {
Expand All @@ -202,6 +200,7 @@ const config: Config = {
rehypeCodeblockMeta,
{ match: { snack: true, lang: true, tabs: true } },
],
[rehypeMediaAttributes, { staticDir: 'static' }],
],
},
pages: {
Expand All @@ -210,34 +209,9 @@ const config: Config = {
theme: {
customCss: './src/css/custom.css',
},
googleAnalytics: {
trackingID: 'UA-10128745-16',
},
} satisfies Preset.Options,
],
],
headTags: [
{
tagName: 'link',
attributes: {
rel: 'preconnect',
href: 'https://fonts.googleapis.com',
},
},
{
tagName: 'link',
attributes: {
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossOrigin: 'true',
},
},
],
scripts: [
'/js/snack-helpers.js',
'/js/toc-fixes.js',
'/js/video-playback.js',
],
};

export default config;
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
"fetch-sponsors": "node scripts/fetch-sponsors.js && prettier --write src/data/sponsors.js"
},
"dependencies": {
"@docusaurus/core": "3.10.1",
"@docusaurus/faster": "^3.10.1",
"@docusaurus/plugin-client-redirects": "3.10.1",
"@docusaurus/plugin-google-analytics": "3.10.1",
"@docusaurus/preset-classic": "3.10.1",
"@docusaurus/remark-plugin-npm2yarn": "3.10.1",
"@docusaurus/core": "3.10.2",
"@docusaurus/faster": "^3.10.2",
"@docusaurus/plugin-client-redirects": "3.10.2",
"@docusaurus/plugin-google-analytics": "3.10.2",
"@docusaurus/preset-classic": "3.10.2",
"@docusaurus/remark-plugin-npm2yarn": "3.10.2",
"@mdx-js/react": "^3.1.1",
"@octokit/graphql": "^9.0.3",
"@react-navigation/core": "^7.13.5",
Expand All @@ -40,7 +40,7 @@
},
"devDependencies": {
"@babel/types": "^7.28.5",
"@docusaurus/tsconfig": "^3.10.1",
"@docusaurus/tsconfig": "^3.10.2",
"@ffprobe-installer/ffprobe": "^2.1.2",
"@types/node": "^25.2.3",
"@types/react": "^19.2.8",
Expand Down
31 changes: 31 additions & 0 deletions src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ClientModule } from '@docusaurus/types';

let helpers:
| Promise<
[
typeof import('./snack-helpers.ts'),
typeof import('./toc-fixes.ts'),
typeof import('./video-playback.ts'),
]
>
| undefined;

const loadHelpers = () => {
helpers ??= Promise.all([
import('./snack-helpers.ts'),
import('./toc-fixes.ts'),
import('./video-playback.ts'),
]);

return helpers;
};

const clientModule: ClientModule = {
onRouteDidUpdate() {
void loadHelpers().then(([, { syncToc }]) => {
syncToc();
});
},
};

export default clientModule;
49 changes: 40 additions & 9 deletions static/js/snack-helpers.js → src/client/snack-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ const DEPS_VERSIONS = {
],
};

type MajorVersion = keyof typeof DEPS_VERSIONS;

type SnackOptions = {
code: string;
label: string;
templateId: string | null;
};

function isMajorVersion(version: number): version is MajorVersion {
return Object.hasOwn(DEPS_VERSIONS, version);
}

function getVersion() {
const maybeVersion = window.location.pathname.split('/')[1];

Expand All @@ -77,13 +89,17 @@ function getVersion() {
return '6.x';
}

function getSnackUrl(options) {
function getSnackUrl(options: SnackOptions) {
let currentVersion = getVersion();
let label = options.label || document.title;
let code = options.code;
let templateId = options.templateId;

const currentMajorVersion = currentVersion.match(/(\d+)\./)[1];
const currentMajorVersion = Number(currentVersion.match(/(\d+)\./)?.[1]);

if (!isMajorVersion(currentMajorVersion)) {
throw new Error(`Unsupported React Navigation version: ${currentVersion}`);
}

let baseUrl =
`https://snack.expo.io?platform=${DEFAULT_PLATFORM}&name=` +
Expand All @@ -104,14 +120,17 @@ function getSnackUrl(options) {
}
}

function findNearestCodeBlock(node) {
function findNearestCodeBlock(node: HTMLElement) {
let nextElement = node.nextElementSibling;
if (!nextElement && node.parentElement.tagName === 'P') {
nextElement = node.parentElement.nextElementSibling;
const parent = node.parentElement;

if (!nextElement && parent?.tagName === 'P') {
nextElement = parent.nextElementSibling;
}

while (nextElement) {
if (
nextElement instanceof HTMLElement &&
nextElement.tagName === 'DIV' &&
(nextElement.className.includes('mdxCodeBlock') ||
nextElement.className.includes('codeBlockContainer'))
Expand All @@ -127,7 +146,7 @@ let openIcon =
'<svg width="14px" height="14px" viewBox="0 0 16 16" style="vertical-align: -1px"><g stroke="none" stroke-width="1" fill="none"><polyline stroke="currentColor" points="8.5 0.5 15.5 0.5 15.5 7.5"></polyline><path d="M8,8 L15.0710678,0.928932188" stroke="currentColor"></path><polyline stroke="currentColor" points="9.06944444 3.5 1.5 3.5 1.5 14.5 12.5 14.5 12.5 6.93055556"></polyline></g></svg>';

function appendSnackLink() {
let samples = document.querySelectorAll('samp');
let samples = document.querySelectorAll<HTMLElement>('samp');

if (!samples.length) {
return;
Expand All @@ -151,7 +170,7 @@ function appendSnackLink() {

let link = document.createElement('a');
link.className = 'code-block-snack-link';
link.dataset.snack = true;
link.dataset.snack = 'true';
link.target = '_blank';

if (label) {
Expand Down Expand Up @@ -188,7 +207,17 @@ function initializeSnackObservers() {
appendSnackLink();

const mutationObserver = new MutationObserver((mutations) => {
mutations.forEach(appendSnackLink);
if (
mutations.some((mutation) =>
Array.from(mutation.addedNodes).some(
(node) =>
node instanceof Element &&
(node.matches('samp') || node.querySelector('samp'))
)
)
) {
appendSnackLink();
}
});

mutationObserver.observe(document.documentElement, {
Expand All @@ -197,4 +226,6 @@ function initializeSnackObservers() {
});
}

document.addEventListener('DOMContentLoaded', initializeSnackObservers);
initializeSnackObservers();

export {};
37 changes: 37 additions & 0 deletions src/client/toc-fixes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Docusaurus shows items in hidden tabs in the TOC.
* It's confusing since it doesn't represent the actual page.
* And clicking on those items doesn't do anything.
* This scripts syncs the TOCs with visible headings.
*/
export const syncToc = () => {
const headings = document.querySelectorAll<HTMLElement>(
'article :is(h2, h3)'
);

// Get all visible headings
const titles = Array.from(headings)
.filter((el) => el.offsetParent)
.map((el) => trim(el.textContent ?? ''));

const toc = document.querySelectorAll<HTMLAnchorElement>(
'.table-of-contents li > a'
);

// Hide TOC items that don't have a corresponding heading
toc.forEach((el) => {
const item = el.parentElement;

if (!item) {
return;
}

if (!titles.includes(trim(el.textContent ?? ''))) {
item.style.display = 'none';
} else {
item.style.display = 'block';
}
});
};

const trim = (str: string) => str.trim().replace(/[\u200B-\u200D\uFEFF]/g, '');
53 changes: 53 additions & 0 deletions src/client/video-playback.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.player {
position: relative;
display: inline-block;
border-radius: var(--ifm-global-radius);

&.paused .control::after {
background-image: url("data:image/svg+xml,<svg viewBox='0 0 18 18' xmlns='http://www.w3.org/2000/svg'><polygon points='6.5,4 6.5,14 14.5,9' fill='white'/></svg>");
}

&:hover .control,
&.paused .control {
opacity: 1;
}
}

.control {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 16px;
right: 16px;
width: 42px;
height: 42px;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(8px);
transition: 200ms opacity ease;
opacity: 0;
border-radius: 50%;
border: 0;
padding: 0;
color: inherit;
cursor: pointer;

&:focus-visible {
opacity: 1;
outline: 2px solid var(--ifm-color-primary);
outline-offset: 2px;
}

&::after {
content: '';
height: 24px;
aspect-ratio: 1 / 1;
background-image: url("data:image/svg+xml,<svg viewBox='0 0 18 18' xmlns='http://www.w3.org/2000/svg'><rect x='5' y='4' width='2.5' height='10' rx='0.5' fill='white'/><rect x='10.5' y='4' width='2.5' height='10' rx='0.5' fill='white'/></svg>");
}
}

@media (hover: none), (pointer: coarse) {
.control {
opacity: 1;
}
}
Loading
Loading