diff --git a/packages/docusaurus-theme-classic/src/__tests__/__snapshots__/translations.test.ts.snap b/packages/docusaurus-theme-classic/src/__tests__/__snapshots__/translations.test.ts.snap index a0e76dbce6..3f3ae40ed4 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/__snapshots__/translations.test.ts.snap +++ b/packages/docusaurus-theme-classic/src/__tests__/__snapshots__/translations.test.ts.snap @@ -55,6 +55,49 @@ Array [ ] `; +exports[`getTranslationFiles should return translation files matching snapshot 2`] = ` +Array [ + Object { + "content": Object { + "item.label.Dropdown": Object { + "description": "Navbar item with label Dropdown", + "message": "Dropdown", + }, + "item.label.Dropdown item 1": Object { + "description": "Navbar item with label Dropdown item 1", + "message": "Dropdown item 1", + }, + "item.label.Dropdown item 2": Object { + "description": "Navbar item with label Dropdown item 2", + "message": "Dropdown item 2", + }, + "title": Object { + "description": "The title in the navbar", + "message": "navbar title", + }, + }, + "path": "navbar", + }, + Object { + "content": Object { + "copyright": Object { + "description": "The footer copyright", + "message": "Copyright FB", + }, + "link.item.label.Link 1": Object { + "description": "The label of footer link with label=Link 1 linking to https://facebook.com", + "message": "Link 1", + }, + "link.item.label.Link 2": Object { + "description": "The label of footer link with label=Link 2 linking to https://facebook.com", + "message": "Link 2", + }, + }, + "path": "footer", + }, +] +`; + exports[`translateThemeConfig should return translated themeConfig matching snapshot 1`] = ` Object { "announcementBar": Object {}, diff --git a/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts b/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts index 64ecdf8a2e..fd0bfd51ec 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts +++ b/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts @@ -50,14 +50,26 @@ const ThemeConfigSample: ThemeConfig = { }, }; -function getSampleTranslationFiles() { +const ThemeConfigSampleSimpleFooter: ThemeConfig = { + ...ThemeConfigSample, + footer: { + copyright: 'Copyright FB', + style: 'light', + links: [ + {label: 'Link 1', to: 'https://facebook.com'}, + {label: 'Link 2', to: 'https://facebook.com'}, + ], + }, +}; + +function getSampleTranslationFiles(themeConfig: ThemeConfig) { return getTranslationFiles({ - themeConfig: ThemeConfigSample, + themeConfig, }); } -function getSampleTranslationFilesTranslated() { - const translationFiles = getSampleTranslationFiles(); +function getSampleTranslationFilesTranslated(themeConfig: ThemeConfig) { + const translationFiles = getSampleTranslationFiles(themeConfig); return translationFiles.map((translationFile) => updateTranslationFileMessages( translationFile, @@ -68,27 +80,29 @@ function getSampleTranslationFilesTranslated() { describe('getTranslationFiles', () => { test('should return translation files matching snapshot', () => { - expect(getSampleTranslationFiles()).toMatchSnapshot(); + expect(getSampleTranslationFiles(ThemeConfigSample)).toMatchSnapshot(); + expect( + getSampleTranslationFiles(ThemeConfigSampleSimpleFooter), + ).toMatchSnapshot(); }); }); describe('translateThemeConfig', () => { test('should not translate anything if translation files are untranslated', () => { - const translationFiles = getSampleTranslationFiles(); expect( translateThemeConfig({ themeConfig: ThemeConfigSample, - translationFiles, + translationFiles: getSampleTranslationFiles(ThemeConfigSample), }), ).toEqual(ThemeConfigSample); }); test('should return translated themeConfig matching snapshot', () => { - const translationFiles = getSampleTranslationFilesTranslated(); expect( translateThemeConfig({ themeConfig: ThemeConfigSample, - translationFiles, + translationFiles: + getSampleTranslationFilesTranslated(ThemeConfigSample), }), ).toMatchSnapshot(); }); @@ -96,18 +110,21 @@ describe('translateThemeConfig', () => { describe('getTranslationFiles and translateThemeConfig isomorphism', () => { function verifyIsomorphism(themeConfig: ThemeConfig) { - const translationFiles = getTranslationFiles({themeConfig}); const translatedThemeConfig = translateThemeConfig({ themeConfig, - translationFiles, + translationFiles: getTranslationFiles({themeConfig}), }); expect(translatedThemeConfig).toEqual(themeConfig); } - test('should be verified for main sample', () => { + test('should be verified for sample', () => { verifyIsomorphism(ThemeConfigSample); }); + test('should be verified for sample with simple footer', () => { + verifyIsomorphism(ThemeConfigSampleSimpleFooter); + }); + // undefined footer should not make the translation code crash // See https://github.com/facebook/docusaurus/issues/3936 test('should be verified for sample without footer', () => { diff --git a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js index afea10a29f..e987f2ac69 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js +++ b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js @@ -345,6 +345,104 @@ describe('themeConfig', () => { }); }); + test('should allow simple links in footer', () => { + const partialConfig = { + footer: { + links: [ + { + label: 'Privacy', + href: 'https://opensource.facebook.com/legal/privacy/', + }, + { + label: 'Terms', + href: 'https://opensource.facebook.com/legal/terms/', + }, + { + label: 'Data Policy', + href: 'https://opensource.facebook.com/legal/data-policy/', + }, + { + label: 'Cookie Policy', + href: 'https://opensource.facebook.com/legal/cookie-policy/', + }, + ], + }, + }; + const normalizedConfig = testValidateThemeConfig(partialConfig); + + expect(normalizedConfig).toEqual({ + ...normalizedConfig, + footer: { + ...normalizedConfig.footer, + ...partialConfig.footer, + }, + }); + }); + + test('should allow footer column with no title', () => { + const partialConfig = { + footer: { + links: [ + { + items: [ + { + label: 'Data Policy', + href: 'https://opensource.facebook.com/legal/data-policy/', + }, + { + label: 'Cookie Policy', + href: 'https://opensource.facebook.com/legal/cookie-policy/', + }, + ], + }, + ], + }, + }; + const normalizedConfig = testValidateThemeConfig(partialConfig); + + expect(normalizedConfig).toEqual({ + ...normalizedConfig, + footer: { + ...normalizedConfig.footer, + ...partialConfig.footer, + links: [ + { + title: null, // Default value is important to distinguish simple footer from multi-column footer + items: partialConfig.footer.links[0].items, + }, + ], + }, + }); + }); + + test('should reject mix of simple and multi-column links in footer', () => { + const partialConfig = { + footer: { + links: [ + { + title: 'Learn', + items: [ + { + label: 'Introduction', + to: 'docs', + }, + ], + }, + { + label: 'Privacy', + href: 'https://opensource.facebook.com/legal/privacy/', + }, + ], + }, + }; + + expect(() => + testValidateThemeConfig(partialConfig), + ).toThrowErrorMatchingInlineSnapshot( + `"The footer must be either simple or multi-column, and not a mix of the two. See: https://docusaurus.io/docs/api/themes/configuration#footer-links"`, + ); + }); + test('should allow width and height specification for logo ', () => { const altTagConfig = { navbar: { diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx index 026afe6115..0f154f8fe0 100644 --- a/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx @@ -9,7 +9,12 @@ import React from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; -import {FooterLinkItem, useThemeConfig} from '@docusaurus/theme-common'; +import { + FooterLinkItem, + useThemeConfig, + MultiColumnFooter, + SimpleFooter, +} from '@docusaurus/theme-common'; import useBaseUrl from '@docusaurus/useBaseUrl'; import isInternalUrl from '@docusaurus/isInternalUrl'; import styles from './styles.module.css'; @@ -66,6 +71,70 @@ function FooterLogo({ ); } +function MultiColumnLinks({links}: {links: MultiColumnFooter['links']}) { + return ( + <> + {links.map((linkItem, i) => ( +
'dark' \| 'light'
| `'light'` | The color theme of the footer component. |
-| `items` | `FooterItem[]` | `[]` | The link groups to be present. |
+| `links` | (Column \| FooterLink)[]
| `[]` | The link groups to be present. |
@@ -699,20 +699,20 @@ module.exports = {
### Footer Links {#footer-links}
-You can add links to the footer via `themeConfig.footer.links`.
+You can add links to the footer via `themeConfig.footer.links`. There are two types of footer configurations: **multi-column footers** and **simple footers**.
-Accepted fields of each link section:
+Multi-column footer links have a `title` and a list of `FooterItem`s for each column.