mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-17 19:16:58 +02:00
refactor(theme-classic): split theme footer into smaller components + swizzle config (#6894)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
This commit is contained in:
parent
c9ee6e467c
commit
1efc6c6091
14 changed files with 416 additions and 180 deletions
|
@ -37,11 +37,62 @@ export default function getSwizzleConfig(): SwizzleConfig {
|
|||
},
|
||||
Footer: {
|
||||
actions: {
|
||||
eject: 'unsafe', // TODO split footer into smaller parts
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: "The footer component of you site's layout",
|
||||
},
|
||||
'Footer/Copyright': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The footer copyright',
|
||||
},
|
||||
'Footer/Layout': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The footer main layout component',
|
||||
},
|
||||
'Footer/LinkItem': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The footer link item component',
|
||||
},
|
||||
'Footer/Links': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The footer component rendering the footer links',
|
||||
},
|
||||
'Footer/Links/MultiColumn': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The footer component rendering the footer links with a multi-column layout',
|
||||
},
|
||||
'Footer/Links/Simple': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The footer component rendering the footer links with a simple layout (single row)',
|
||||
},
|
||||
'Footer/Logo': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The footer logo',
|
||||
},
|
||||
IconArrow: {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
|
|
|
@ -205,7 +205,7 @@ declare module '@theme/DocSidebar/Desktop/Content' {
|
|||
readonly sidebar: readonly PropSidebarItem[];
|
||||
}
|
||||
|
||||
export default function CollapseButton(props: Props): JSX.Element;
|
||||
export default function Content(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/DocSidebar/Desktop/CollapseButton' {
|
||||
|
@ -280,6 +280,77 @@ declare module '@theme/Footer' {
|
|||
export default function Footer(): JSX.Element | null;
|
||||
}
|
||||
|
||||
declare module '@theme/Footer/Logo' {
|
||||
import type {FooterLogo} from '@docusaurus/theme-common';
|
||||
|
||||
export interface Props {
|
||||
logo: FooterLogo;
|
||||
}
|
||||
|
||||
export default function FooterLogo(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Footer/Copyright' {
|
||||
export interface Props {
|
||||
copyright: string;
|
||||
}
|
||||
|
||||
export default function FooterCopyright(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Footer/LinkItem' {
|
||||
import type {FooterLinkItem} from '@docusaurus/theme-common';
|
||||
|
||||
export interface Props {
|
||||
item: FooterLinkItem;
|
||||
}
|
||||
|
||||
export default function FooterLinkItem(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Footer/Layout' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
export interface Props {
|
||||
style: 'light' | 'dark';
|
||||
links: ReactNode;
|
||||
logo: ReactNode;
|
||||
copyright: ReactNode;
|
||||
}
|
||||
|
||||
export default function FooterLayout(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Footer/Links' {
|
||||
import type {Footer} from '@docusaurus/theme-common';
|
||||
|
||||
export interface Props {
|
||||
links: Footer['links'];
|
||||
}
|
||||
|
||||
export default function FooterLinks(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Footer/Links/MultiColumn' {
|
||||
import type {MultiColumnFooter} from '@docusaurus/theme-common';
|
||||
|
||||
export interface Props {
|
||||
columns: MultiColumnFooter['links'];
|
||||
}
|
||||
|
||||
export default function FooterLinksMultiColumn(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Footer/Links/Simple' {
|
||||
import type {SimpleFooter} from '@docusaurus/theme-common';
|
||||
|
||||
export interface Props {
|
||||
links: SimpleFooter['links'];
|
||||
}
|
||||
|
||||
export default function FooterLinksSimple(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Heading' {
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type {Props} from '@theme/Footer/Copyright';
|
||||
|
||||
export default function FooterCopyright({copyright}: Props): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className="footer__copyright"
|
||||
// Developer provided the HTML, so assume it's safe.
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: copyright,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import type {Props} from '@theme/Footer/Layout';
|
||||
|
||||
export default function FooterLayout({
|
||||
style,
|
||||
links,
|
||||
logo,
|
||||
copyright,
|
||||
}: Props): JSX.Element {
|
||||
return (
|
||||
<footer
|
||||
className={clsx('footer', {
|
||||
'footer--dark': style === 'dark',
|
||||
})}>
|
||||
<div className="container container-fluid">
|
||||
{links}
|
||||
{(logo || copyright) && (
|
||||
<div className="footer__bottom text--center">
|
||||
{logo && <div className="margin-bottom--sm">{logo}</div>}
|
||||
{copyright}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import isInternalUrl from '@docusaurus/isInternalUrl';
|
||||
import IconExternalLink from '@theme/IconExternalLink';
|
||||
import type {Props} from '@theme/Footer/LinkItem';
|
||||
|
||||
export default function FooterLinkItem({item}: Props): JSX.Element {
|
||||
const {to, href, label, prependBaseUrlToHref, ...props} = item;
|
||||
const toUrl = useBaseUrl(to);
|
||||
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
|
||||
|
||||
return (
|
||||
<Link
|
||||
className="footer__link-item"
|
||||
{...(href
|
||||
? {
|
||||
href: prependBaseUrlToHref ? normalizedHref : href,
|
||||
}
|
||||
: {
|
||||
to: toUrl,
|
||||
})}
|
||||
{...props}>
|
||||
<span>
|
||||
{label}
|
||||
{href && !isInternalUrl(href) && <IconExternalLink />}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type {Props} from '@theme/Footer/Links/MultiColumn';
|
||||
import LinkItem from '@theme/Footer/LinkItem';
|
||||
|
||||
type ColumnType = Props['columns'][number];
|
||||
type ColumnItemType = ColumnType['items'][number];
|
||||
|
||||
function ColumnLinkItem({item}: {item: ColumnItemType}) {
|
||||
return item.html ? (
|
||||
<li
|
||||
className="footer__item"
|
||||
// Developer provided the HTML, so assume it's safe.
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.html,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<li key={item.href || item.to} className="footer__item">
|
||||
<LinkItem item={item} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function Column({column}: {column: ColumnType}) {
|
||||
return (
|
||||
<div className="col footer__col">
|
||||
<div className="footer__title">{column.title}</div>
|
||||
<ul className="footer__items">
|
||||
{column.items.map((item, i) => (
|
||||
<ColumnLinkItem key={i} item={item} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FooterLinksMultiColumn({columns}: Props): JSX.Element {
|
||||
return (
|
||||
<div className="row footer__links">
|
||||
{columns.map((column, i) => (
|
||||
<Column key={i} column={column} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type {Props} from '@theme/Footer/Links/Simple';
|
||||
import LinkItem from '@theme/Footer/LinkItem';
|
||||
|
||||
function Separator() {
|
||||
return <span className="footer__link-separator">·</span>;
|
||||
}
|
||||
|
||||
function SimpleLinkItem({item}: {item: Props['links'][number]}) {
|
||||
return item.html ? (
|
||||
<span
|
||||
className="footer__link-item"
|
||||
// Developer provided the HTML, so assume it's safe.
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.html,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<LinkItem item={item} />
|
||||
);
|
||||
}
|
||||
|
||||
export default function FooterLinksSimple({links}: Props): JSX.Element {
|
||||
return (
|
||||
<div className="footer__links text--center">
|
||||
<div className="footer__links">
|
||||
{links.map((item, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<SimpleLinkItem item={item} />
|
||||
{links.length !== i + 1 && <Separator />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {isMultiColumnFooterLinks} from '@docusaurus/theme-common';
|
||||
import type {Props} from '@theme/Footer/Links';
|
||||
import FooterLinksMultiColumn from '@theme/Footer/Links/MultiColumn';
|
||||
import FooterLinksSimple from '@theme/Footer/Links/Simple';
|
||||
|
||||
export default function FooterLinks({links}: Props): JSX.Element {
|
||||
return isMultiColumnFooterLinks(links) ? (
|
||||
<FooterLinksMultiColumn columns={links} />
|
||||
) : (
|
||||
<FooterLinksSimple links={links} />
|
||||
);
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||
import styles from './styles.module.css';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import type {Props} from '@theme/Footer/Logo';
|
||||
|
||||
function LogoImage({logo}: Props) {
|
||||
const {withBaseUrl} = useBaseUrlUtils();
|
||||
const sources = {
|
||||
light: withBaseUrl(logo.src),
|
||||
dark: withBaseUrl(logo.srcDark ?? logo.src),
|
||||
};
|
||||
return (
|
||||
<ThemedImage
|
||||
className="footer__logo"
|
||||
alt={logo.alt}
|
||||
sources={sources}
|
||||
width={logo.width}
|
||||
height={logo.height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FooterLogo({logo}: Props): JSX.Element {
|
||||
return logo.href ? (
|
||||
<Link href={logo.href} className={styles.footerLogoLink}>
|
||||
<LogoImage logo={logo} />
|
||||
</Link>
|
||||
) : (
|
||||
<LogoImage logo={logo} />
|
||||
);
|
||||
}
|
|
@ -6,185 +6,27 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import {
|
||||
type FooterLinkItem,
|
||||
useThemeConfig,
|
||||
type MultiColumnFooter,
|
||||
type SimpleFooter,
|
||||
} from '@docusaurus/theme-common';
|
||||
import useBaseUrl, {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||
import isInternalUrl from '@docusaurus/isInternalUrl';
|
||||
import styles from './styles.module.css';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import IconExternalLink from '@theme/IconExternalLink';
|
||||
|
||||
function FooterLink({
|
||||
to,
|
||||
href,
|
||||
label,
|
||||
prependBaseUrlToHref,
|
||||
...props
|
||||
}: FooterLinkItem) {
|
||||
const toUrl = useBaseUrl(to);
|
||||
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
|
||||
|
||||
return (
|
||||
<Link
|
||||
className="footer__link-item"
|
||||
{...(href
|
||||
? {
|
||||
href: prependBaseUrlToHref ? normalizedHref : href,
|
||||
}
|
||||
: {
|
||||
to: toUrl,
|
||||
})}
|
||||
{...props}>
|
||||
<span>
|
||||
{label}
|
||||
{href && !isInternalUrl(href) && <IconExternalLink />}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function FooterLogo({logo}: {logo: SimpleFooter['logo']}) {
|
||||
const {withBaseUrl} = useBaseUrlUtils();
|
||||
if (!logo?.src) {
|
||||
return null;
|
||||
}
|
||||
const sources = {
|
||||
light: withBaseUrl(logo.src),
|
||||
dark: withBaseUrl(logo.srcDark ?? logo.src),
|
||||
};
|
||||
const image = (
|
||||
<ThemedImage
|
||||
className="footer__logo"
|
||||
alt={logo.alt}
|
||||
sources={sources}
|
||||
width={logo.width}
|
||||
height={logo.height}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div className="margin-bottom--sm">
|
||||
{logo.href ? (
|
||||
<Link href={logo.href} className={styles.footerLogoLink}>
|
||||
{image}
|
||||
</Link>
|
||||
) : (
|
||||
image
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MultiColumnLinks({links}: {links: MultiColumnFooter['links']}) {
|
||||
return (
|
||||
<>
|
||||
{links.map((linkItem, i) => (
|
||||
<div key={i} className="col footer__col">
|
||||
<div className="footer__title">{linkItem.title}</div>
|
||||
<ul className="footer__items">
|
||||
{linkItem.items.map((item, key) =>
|
||||
item.html ? (
|
||||
<li
|
||||
key={key}
|
||||
className="footer__item"
|
||||
// Developer provided the HTML, so assume it's safe.
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.html,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<li key={item.href || item.to} className="footer__item">
|
||||
<FooterLink {...item} />
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SimpleLinks({links}: {links: SimpleFooter['links']}) {
|
||||
return (
|
||||
<div className="footer__links">
|
||||
{links.map((item, key) => (
|
||||
<React.Fragment key={key}>
|
||||
{item.html ? (
|
||||
<span
|
||||
className="footer__link-item"
|
||||
// Developer provided the HTML, so assume it's safe.
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.html,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FooterLink {...item} />
|
||||
)}
|
||||
{links.length !== key + 1 && (
|
||||
<span className="footer__link-separator">·</span>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function isMultiColumnFooterLinks(
|
||||
links: MultiColumnFooter['links'] | SimpleFooter['links'],
|
||||
): links is MultiColumnFooter['links'] {
|
||||
return 'title' in links[0]!;
|
||||
}
|
||||
import {useThemeConfig} from '@docusaurus/theme-common';
|
||||
import FooterLinks from '@theme/Footer/Links';
|
||||
import FooterLogo from '@theme/Footer/Logo';
|
||||
import FooterCopyright from '@theme/Footer/Copyright';
|
||||
import FooterLayout from '@theme/Footer/Layout';
|
||||
|
||||
function Footer(): JSX.Element | null {
|
||||
const {footer} = useThemeConfig();
|
||||
if (!footer) {
|
||||
return null;
|
||||
}
|
||||
const {copyright, links, logo} = footer;
|
||||
const {copyright, links, logo, style} = footer;
|
||||
|
||||
return (
|
||||
<footer
|
||||
className={clsx('footer', {
|
||||
'footer--dark': footer.style === 'dark',
|
||||
})}>
|
||||
<div className="container container-fluid">
|
||||
{links &&
|
||||
links.length > 0 &&
|
||||
(isMultiColumnFooterLinks(links) ? (
|
||||
<div className="row footer__links">
|
||||
<MultiColumnLinks links={links} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="footer__links text--center">
|
||||
<SimpleLinks links={links} />
|
||||
</div>
|
||||
))}
|
||||
{(logo || copyright) && (
|
||||
<div className="footer__bottom text--center">
|
||||
<FooterLogo logo={logo} />
|
||||
{copyright && (
|
||||
<div
|
||||
className="footer__copyright"
|
||||
// Developer provided the HTML, so assume it's safe.
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: copyright,
|
||||
}}
|
||||
<FooterLayout
|
||||
style={style}
|
||||
links={links && links.length > 0 && <FooterLinks links={links} />}
|
||||
logo={logo && <FooterLogo logo={logo} />}
|
||||
copyright={copyright && <FooterCopyright copyright={copyright} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ export type {
|
|||
MultiColumnFooter,
|
||||
SimpleFooter,
|
||||
Footer,
|
||||
FooterLogo,
|
||||
FooterLinkItem,
|
||||
ColorModeConfig,
|
||||
} from './utils/useThemeConfig';
|
||||
|
@ -110,6 +111,8 @@ export {
|
|||
type TOCTreeNode,
|
||||
} from './utils/tocUtils';
|
||||
|
||||
export {isMultiColumnFooterLinks} from './utils/footerUtils';
|
||||
|
||||
export {
|
||||
ScrollControllerProvider,
|
||||
useScrollController,
|
||||
|
|
14
packages/docusaurus-theme-common/src/utils/footerUtils.ts
Normal file
14
packages/docusaurus-theme-common/src/utils/footerUtils.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {MultiColumnFooter, SimpleFooter} from './useThemeConfig';
|
||||
|
||||
export function isMultiColumnFooterLinks(
|
||||
links: MultiColumnFooter['links'] | SimpleFooter['links'],
|
||||
): links is MultiColumnFooter['links'] {
|
||||
return 'title' in links[0]!;
|
||||
}
|
|
@ -65,18 +65,20 @@ export type FooterLinkItem = {
|
|||
href?: string;
|
||||
html?: string;
|
||||
prependBaseUrlToHref?: string;
|
||||
};
|
||||
} & Record<string, unknown>;
|
||||
|
||||
export type FooterBase = {
|
||||
style: 'light' | 'dark';
|
||||
logo?: {
|
||||
export type FooterLogo = {
|
||||
alt?: string;
|
||||
src: string;
|
||||
srcDark?: string;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
href?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type FooterBase = {
|
||||
style: 'light' | 'dark';
|
||||
logo?: FooterLogo;
|
||||
copyright?: string;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue