Custom Theme
This article will tell you how to develop custom theme.
Extensions based on the default theme
In most cases, you don't want to develop a theme from scratch, but want to extend it based on the default theme. At this time, you can refer to the following methods for theme development.
1. Basic structure
By default, you need to create a theme
directory under the project root directory, and then create an index.ts
or index.tsx
file under the theme
directory, which is used to export the theme content.
You can write the theme/index.tsx
file as follows:
theme/index.tsx
import Theme from 'rspress/theme';
const Layout = () => <Theme.Layout beforeNavTitle={<div>some content</div>} />;
export default {
...Theme,
Layout,
};
export * from 'rspress/theme';
On the one hand, you need to export a theme configuration object through export default
, on the other hand, you need to export all named exported content through export * from 'rspress/theme'
so as to ensure your theme works fine.
2. Use slot
It is worth noting that the Layout
component has designed a series of props to support slot elements. You can use these props to extend the layout of the default theme. For example, change the above Layout
component to the following form:
theme/index.tsx
import Theme from 'rspress/theme';
// Show all props below
const Layout = () => (
<Theme.Layout
/* Before home hero */
beforeHero={<div>beforeHero</div>}
/* After home hero */
afterHero={<div>afterHero</div>}
/* Before home features */
beforeFeatures={<div>beforeFeatures</div>}
/* After home features */
afterFeatures={<div>afterFeatures</div>}
/* Before doc footer */
beforeDocFooter={<div>beforeDocFooter</div>}
/* After doc footer */
afterDocFooter={<div>afterDocFooter</div>}
/* Doc page front */
beforeDoc={<div>beforeDoc</div>}
/* Doc page end */
afterDoc={<div>afterDoc</div>}
/* Doc content front */
beforeDocContent={<div>beforeDocContent</div>}
/* Doc content end */
afterDocContent={<div>afterDocContent</div>}
/* Before the nav bar */
beforeNav={<div>beforeNav</div>}
/* Before the title of the nav bar in the upper left corner */
beforeNavTitle={<span>😄</span>}
/* Nav bar title */
navTitle={<div>Custom Nav Title</div>}
/* After the title of the nav bar in the upper left corner */
afterNavTitle={<div>afterNavTitle</div>}
/* The right corner of the nav menu */
afterNavMenu={<div>afterNavMenu</div>}
/* Above the left sidebar */
beforeSidebar={<div>beforeSidebar</div>}
/* Below the left sidebar */
afterSidebar={<div>afterSidebar</div>}
/* Above the right outline column */
beforeOutline={<div>beforeOutline</div>}
/* Below the outline column on the right */
afterOutline={<div>afterOutline</div>}
/* Top of the entire page */
top={<div>top</div>}
/* Bottom of the entire page */
bottom={<div>bottom</div>}
/* Custom MDX components */
components={{ p: props => <p {...props} className="my-4 leading-7" /> }}
/>
);
export default {
...Theme,
Layout,
};
export * from 'rspress/theme';
3. Custom Home page and 404 page
In addition to the slot method, if you want to extend the default theme components, you can also customize the Home page components and 404 page components,
as well as other Rspress built-in components
theme/index.tsx
import Theme, { Search } from 'rspress/theme';
// Use slot
const Layout = () => <Theme.Layout beforeNavTitle={<div>some content</div>} />;
// Custom Home Page
const HomeLayout = () => <div>Home</div>;
// Custom 404 page
const NotFoundLayout = () => <div>404</div>;
export default {
...Theme,
Layout,
HomeLayout,
NotFoundLayout,
};
// Custom Search Component
const MySearch = () => <div className="my-search">{Search}</div>;
export { MySearch as Search };
// re-export
export * from 'rspress/theme';
Of course, you may need to use page data during the development process, you can get it through Runtime API
.
4. Custom Icon
If you want to modify the icons used in the default theme component individually, just put the icons with the same name in the theme/assets directory.
└── theme
└── assets
└── xx.svg
You can view all the icons used in the default theme here.
Redevelop a custom theme
If you're developing a custom theme from scratch, you need to understand the basic structure of the theme and the runtime API.
1. Basic structure
By default, you need to create a theme
directory under the project root directory, and then create an index.ts
or index.tsx
file under the theme
directory, which is used to export the theme content.
├── theme
│ └── index.tsx
In the theme/index.tsx
file, you need to export a Layout component, which is the entry component of your theme:
// theme/index.tsx
function Layout() {
return <div>Custom Theme Layout</div>;
}
// The setup function will be called when the page is initialized. It is generally used to monitor global events, and it can be an empty function
const setup = () => {};
// Export all content of the default theme to ensure that your theme configuration can work properly
export * from 'rspress/theme';
// Export Layout component and setup function
// Note: both must export
export default { Layout, setup };
Layout component will be used to render the layout of the entire document site, you can introduce your custom components in this component, for example:
// theme/index.tsx
import { Navbar } from './Navbar';
function Layout() {
return (
<div>
<Navbar />
<div>Custom Theme Layout</div>
</div>
);
}
const setup = () => {};
export * from 'rspress/theme';
export { Layout, setup };
// theme/Navbar.tsx
export function Navbar() {
return <div>Custom Navbar</div>;
}
So the question is, how does the theme component get the page data and the content of the body MDX component? Next, let's take a look at the related APIs.
2. Runtime API
usePageData
Get information about all data on the site, such as:
import { usePageData } from 'rspress/runtime';
function MyComponent() {
const pageData = usePageData();
return <div>{pageData.title}</div>;
}
useLang
Get the current language information, such as:
import { useLang } from 'rspress/runtime';
function MyComponent() {
const lang = useLang();
return <div>{lang}</div>;
}
Content
Get MDX component content, such as:
import { Content } from 'rspress/runtime';
function Layout() {
return (
<div>
<Content />
</div>
);
}
Route Hook
react-router-dom is used inside the framework to implement routing, so you can directly use the Hook of react-router-dom
, for example:
import { useLocation } from 'rspress/runtime';
function Layout() {
const location = useLocation();
return <div>Current location: {location.pathname}</div>;
}
3. Reusing Search Functionality
The default theme comes with built-in search functionality, which we can break down into two components:
- The search box, i.e., the entry point to invoke the search.
- The search panel that pops up after clicking on the search box.
Full Reuse
If you want to fully reuse the search functionality, you can directly import the Search
component, like so:
import { Search } from 'rspress/theme';
function MySearch() {
return <Search />;
}
Reusing the Search Panel
If you only want to reuse the search panel and customize the search box part, then you need to import the SearchPanel
component in your theme component, like so:
import { useState } from 'react';
import { SearchPanel } from 'rspress/theme';
function MySearch() {
const [focused, setFocused] = useState(false);
return (
<div>
<button onClick={() => setFocused(true)}>Toggle Search</button>
<SearchPanel focused={focused} setFocused={setFocused} />
</div>
);
}
In this case, you need to maintain the focused
state and setFocused
method yourself, and pass them as props to the SearchPanel
component for controlling the display and hiding of the search panel.
Reuse Default Full Text Search Logic
If you want to reuse the default full text search logic, you can use the useFullTextSearch
Hook, for example:
import { useFullTextSearch } from 'rspress/theme';
function MySearch() {
const { initialized, search } = useFullTextSearch();
useEffect(() => {
async function searchSomeKeywords(keywords: string) {
if (initialized) {
// Search for keywords
const results = await search(keywords);
console.log(results);
}
}
searchSomeKeywords('keyword');
}, [initialized]);
return <div>Search</div>;
}
Here, initialized
indicates whether the search is initialized, the search
method is used to search for keywords, returning a Promise, the result of the Promise is the result of the default full text search.
It should be noted that the useFullTextSearch
Hook will automatically load the search index during initialization, so you need to first determine the initialized
status, ensure that initialization is complete, and then call the search
method.
The type definition of the search method is as follows:
type SearchFn = (keywords: string, limit?: number) => Promise<SearchResult>;
The limit represents the maximum number of search results, default is 7, which means by default it returns a maximum of seven article results.