feat(theme): add theme layout stable CSS classes (#10945)

This commit is contained in:
Sébastien Lorber 2025-02-21 19:03:49 +01:00 committed by GitHub
parent c10a18d51d
commit ca035d8562
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 83 additions and 13 deletions

View file

@ -6,7 +6,8 @@
*/ */
import React, {type ReactNode} from 'react'; import React, {type ReactNode} from 'react';
import {useThemeConfig} from '@docusaurus/theme-common'; import clsx from 'clsx';
import {ThemeClassNames, useThemeConfig} from '@docusaurus/theme-common';
import {useAnnouncementBar} from '@docusaurus/theme-common/internal'; import {useAnnouncementBar} from '@docusaurus/theme-common/internal';
import AnnouncementBarCloseButton from '@theme/AnnouncementBar/CloseButton'; import AnnouncementBarCloseButton from '@theme/AnnouncementBar/CloseButton';
import AnnouncementBarContent from '@theme/AnnouncementBar/Content'; import AnnouncementBarContent from '@theme/AnnouncementBar/Content';
@ -22,7 +23,10 @@ export default function AnnouncementBar(): ReactNode {
const {backgroundColor, textColor, isCloseable} = announcementBar!; const {backgroundColor, textColor, isCloseable} = announcementBar!;
return ( return (
<div <div
className={styles.announcementBar} className={clsx(
ThemeClassNames.announcementBar.container,
styles.announcementBar,
)}
style={{backgroundColor, color: textColor}} style={{backgroundColor, color: textColor}}
role="banner"> role="banner">
{isCloseable && <div className={styles.announcementBarPlaceholder} />} {isCloseable && <div className={styles.announcementBarPlaceholder} />}

View file

@ -7,6 +7,7 @@
import React, {type ReactNode} from 'react'; import React, {type ReactNode} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import {ThemeClassNames} from '@docusaurus/theme-common';
import type {Props} from '@theme/Footer/Layout'; import type {Props} from '@theme/Footer/Layout';
export default function FooterLayout({ export default function FooterLayout({
@ -17,7 +18,7 @@ export default function FooterLayout({
}: Props): ReactNode { }: Props): ReactNode {
return ( return (
<footer <footer
className={clsx('footer', { className={clsx(ThemeClassNames.layout.footer.container, 'footer', {
'footer--dark': style === 'dark', 'footer--dark': style === 'dark',
})}> })}>
<div className="container container-fluid"> <div className="container container-fluid">

View file

@ -7,6 +7,7 @@
import React, {type ReactNode} from 'react'; import React, {type ReactNode} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import {ThemeClassNames} from '@docusaurus/theme-common';
import LinkItem from '@theme/Footer/LinkItem'; import LinkItem from '@theme/Footer/LinkItem';
import type {Props} from '@theme/Footer/Links/MultiColumn'; import type {Props} from '@theme/Footer/Links/MultiColumn';
@ -30,7 +31,12 @@ function ColumnLinkItem({item}: {item: ColumnItemType}) {
function Column({column}: {column: ColumnType}) { function Column({column}: {column: ColumnType}) {
return ( return (
<div className={clsx('col footer__col', column.className)}> <div
className={clsx(
ThemeClassNames.layout.footer.column,
'col footer__col',
column.className,
)}>
<div className="footer__title">{column.title}</div> <div className="footer__title">{column.title}</div>
<ul className="footer__items clean-list"> <ul className="footer__items clean-list">
{column.items.map((item, i) => ( {column.items.map((item, i) => (

View file

@ -48,6 +48,7 @@ export default function Layout(props: Props): ReactNode {
<div <div
id={SkipToContentFallbackId} id={SkipToContentFallbackId}
className={clsx( className={clsx(
ThemeClassNames.layout.main,
ThemeClassNames.wrapper.main, ThemeClassNames.wrapper.main,
styles.mainWrapper, styles.mainWrapper,
wrapperClassName, wrapperClassName,

View file

@ -6,7 +6,12 @@
*/ */
import React, {type ReactNode} from 'react'; import React, {type ReactNode} from 'react';
import {useThemeConfig, ErrorCauseBoundary} from '@docusaurus/theme-common'; import clsx from 'clsx';
import {
useThemeConfig,
ErrorCauseBoundary,
ThemeClassNames,
} from '@docusaurus/theme-common';
import { import {
splitNavbarItems, splitNavbarItems,
useNavbarMobileSidebar, useNavbarMobileSidebar,
@ -55,8 +60,20 @@ function NavbarContentLayout({
}) { }) {
return ( return (
<div className="navbar__inner"> <div className="navbar__inner">
<div className="navbar__items">{left}</div> <div
<div className="navbar__items navbar__items--right">{right}</div> className={clsx(
ThemeClassNames.layout.navbar.containerLeft,
'navbar__items',
)}>
{left}
</div>
<div
className={clsx(
ThemeClassNames.layout.navbar.containerRight,
'navbar__items navbar__items--right',
)}>
{right}
</div>
</div> </div>
); );
} }

View file

@ -7,7 +7,7 @@
import React, {type ComponentProps, type ReactNode} from 'react'; import React, {type ComponentProps, type ReactNode} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import {useThemeConfig} from '@docusaurus/theme-common'; import {ThemeClassNames, useThemeConfig} from '@docusaurus/theme-common';
import { import {
useHideableNavbar, useHideableNavbar,
useNavbarMobileSidebar, useNavbarMobileSidebar,
@ -43,6 +43,7 @@ export default function NavbarLayout({children}: Props): ReactNode {
description: 'The ARIA label for the main navigation', description: 'The ARIA label for the main navigation',
})} })}
className={clsx( className={clsx(
ThemeClassNames.layout.navbar.container,
'navbar', 'navbar',
'navbar--fixed-top', 'navbar--fixed-top',
hideOnScroll && [ hideOnScroll && [

View file

@ -8,8 +8,21 @@
import React, {type ReactNode} from 'react'; import React, {type ReactNode} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import {useNavbarSecondaryMenu} from '@docusaurus/theme-common/internal'; import {useNavbarSecondaryMenu} from '@docusaurus/theme-common/internal';
import {ThemeClassNames} from '@docusaurus/theme-common';
import type {Props} from '@theme/Navbar/MobileSidebar/Layout'; import type {Props} from '@theme/Navbar/MobileSidebar/Layout';
function NavbarMobileSidebarPanel({children}: {children: ReactNode}) {
return (
<div
className={clsx(
ThemeClassNames.layout.navbar.mobileSidebar.panel,
'navbar-sidebar__item menu',
)}>
{children}
</div>
);
}
export default function NavbarMobileSidebarLayout({ export default function NavbarMobileSidebarLayout({
header, header,
primaryMenu, primaryMenu,
@ -17,14 +30,18 @@ export default function NavbarMobileSidebarLayout({
}: Props): ReactNode { }: Props): ReactNode {
const {shown: secondaryMenuShown} = useNavbarSecondaryMenu(); const {shown: secondaryMenuShown} = useNavbarSecondaryMenu();
return ( return (
<div className="navbar-sidebar"> <div
className={clsx(
ThemeClassNames.layout.navbar.mobileSidebar.container,
'navbar-sidebar',
)}>
{header} {header}
<div <div
className={clsx('navbar-sidebar__items', { className={clsx('navbar-sidebar__items', {
'navbar-sidebar__items--show-secondary': secondaryMenuShown, 'navbar-sidebar__items--show-secondary': secondaryMenuShown,
})}> })}>
<div className="navbar-sidebar__item menu">{primaryMenu}</div> <NavbarMobileSidebarPanel>{primaryMenu}</NavbarMobileSidebarPanel>
<div className="navbar-sidebar__item menu">{secondaryMenu}</div> <NavbarMobileSidebarPanel>{secondaryMenu}</NavbarMobileSidebarPanel>
</div> </div>
</div> </div>
); );

View file

@ -27,8 +27,10 @@ export const ThemeClassNames = {
mdxPage: 'mdx-page', mdxPage: 'mdx-page',
}, },
// TODO Docusaurus v4: remove old classes?
wrapper: { wrapper: {
main: 'main-wrapper', main: 'main-wrapper', // replaced by theme-layout-main
// TODO these wrapper class names are now quite useless // TODO these wrapper class names are now quite useless
// TODO do breaking change later in 3.0 // TODO do breaking change later in 3.0
// we already add plugin name/id class on <html>: that's enough // we already add plugin name/id class on <html>: that's enough
@ -36,6 +38,7 @@ export const ThemeClassNames = {
docsPages: 'docs-wrapper', docsPages: 'docs-wrapper',
mdxPages: 'mdx-wrapper', mdxPages: 'mdx-wrapper',
}, },
common: { common: {
editThisPage: 'theme-edit-this-page', editThisPage: 'theme-edit-this-page',
lastUpdated: 'theme-last-updated', lastUpdated: 'theme-last-updated',
@ -47,8 +50,28 @@ export const ThemeClassNames = {
admonitionType: (type: string) => `theme-admonition-${type}`, admonitionType: (type: string) => `theme-admonition-${type}`,
}, },
announcementBar: {
container: 'theme-announcement-bar',
},
layout: { layout: {
// TODO add other stable classNames here navbar: {
container: 'theme-layout-navbar',
containerLeft: 'theme-layout-navbar-left',
containerRight: 'theme-layout-navbar-right',
mobileSidebar: {
container: 'theme-layout-navbar-sidebar',
panel: 'theme-layout-navbar-sidebar-panel',
},
},
main: {
container: 'theme-layout-main',
},
footer: {
container: 'theme-layout-footer',
column: 'theme-layout-footer-column',
},
}, },
/** /**