From b5630359ee07060d4ee9f3222d3b66e82c4841bd Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 25 Mar 2025 13:08:36 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F(frontend)=20Footer=20conf?= =?UTF-8?q?igurable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To have different footer per instance the content of the footer is now configurable from the theme customization file. See THEME_CUSTOMIZATION_FILE_PATH env var. --- CHANGELOG.md | 9 +- docs/assets/footer-configurable.png | Bin 0 -> 19873 bytes docs/theming.md | 23 ++ .../impress/configuration/theme/default.json | 235 +++++++++--------- .../e2e/__tests__/app-impress/config.spec.ts | 4 +- .../e2e/__tests__/app-impress/footer.spec.ts | 154 +++++++++--- .../apps/impress/public/assets/icon-docs.svg | 12 + .../impress/src/core/config/api/useConfig.tsx | 6 + .../impress/src/features/footer/Footer.tsx | 234 +++++++++-------- .../impress/src/features/footer/index.tsx | 1 + .../apps/impress/src/features/footer/types.ts | 28 +++ .../env.d/dev/configuration/theme/demo.json | 6 + 12 files changed, 451 insertions(+), 261 deletions(-) create mode 100644 docs/assets/footer-configurable.png create mode 100644 src/frontend/apps/impress/public/assets/icon-docs.svg create mode 100644 src/frontend/apps/impress/src/features/footer/types.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index cf9798e3..1367bc1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,17 +13,18 @@ and this project adheres to - ✨(back) add endpoint checking media status - ✨(backend) allow setting session cookie age via env var #977 - ✨(backend) allow theme customnization using a configuration file #948 -- ✨ Add a custom callout block to the editor #892 +- ✨(frontend) Add a custom callout block to the editor #892 - 🚩(frontend) version MIT only #911 - ✨(backend) integrate maleware_detection from django-lasuite #936 +- 🏗️(frontend) Footer configurable #959 - 🩺(CI) add lint spell mistakes #954 - 🛂(frontend) block edition to not connected users #945 - 🚸 Let loader during upload analyze #984 ### Changed -- 📝(frontend) Update documentation -- ✅(frontend) Improve tests coverage +- 📝(frontend) Update documentation #949 +- ✅(frontend) Improve tests coverage #949 - ⬆️(docker) upgrade backend image to python 3.13 #973 - ⬆️(docker) upgrade node images to alpine 3.21 - 🐛(y-provider) increase JSON size limits for transcription conversion @@ -31,7 +32,7 @@ and this project adheres to ### Removed -- 🔥(back) remove footer endpoint +- 🔥(back) remove footer endpoint #948 ## [3.2.1] - 2025-05-06 diff --git a/docs/assets/footer-configurable.png b/docs/assets/footer-configurable.png new file mode 100644 index 0000000000000000000000000000000000000000..c79ebea1d0f8980efcc3bc2d82288bf5c38857b7 GIT binary patch literal 19873 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 J);T3K0RTG2cg+9* literal 0 HcmV?d00001 diff --git a/docs/theming.md b/docs/theming.md index 6f8c65a9..2c511d71 100644 --- a/docs/theming.md +++ b/docs/theming.md @@ -30,4 +30,27 @@ body { Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the background color to the custom color you specified. +---- + +# **Footer Configuration** 📝 + +The footer is configurable from the theme customization file. + +### Settings 🔧 + +```shellscript +THEME_CUSTOMIZATION_FILE_PATH= +``` + +### Example of JSON + +The json must follow some rules: https://github.com/suitenumerique/docs/blob/main/src/helm/env.d/dev/configuration/theme/demo.json + +`footer.default` is the fallback if the language is not supported. + +--- +Below is a visual example of a configured footer ⬇️: + +![Footer Configuration Example](./assets/footer-configurable.png) + diff --git a/src/backend/impress/configuration/theme/default.json b/src/backend/impress/configuration/theme/default.json index c34df5c0..29200ada 100644 --- a/src/backend/impress/configuration/theme/default.json +++ b/src/backend/impress/configuration/theme/default.json @@ -1,124 +1,129 @@ { - "footer": { - "default": { - "externalLinks": [ - { - "label": "Github", - "href": "https://github.com/suitenumerique/docs/" - }, - { - "label": "DINUM", - "href": "https://www.numerique.gouv.fr/dinum/" - }, - { - "label": "ZenDiS", - "href": "https://zendis.de/" - }, - { - "label": "BlockNote.js", - "href": "https://www.blocknotejs.org/" - } - ], - "bottomInformation": { - "label": "Unless otherwise stated, all content on this site is under", - "link": { - "label": "licence etalab-2.0", - "href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md" - } - } + "footer": { + "default": { + "logo": { + "src": "/assets/icon-docs.svg", + "width": "54px", + "alt": "Docs Logo", + "withTitle": true }, - "en": { - "legalLinks": [ - { - "label": "Legal Notice", - "href": "#" - }, - { - "label": "Personal data and cookies", - "href": "#" - }, - { - "label": "Accessibility", - "href": "#" - } - ], - "bottomInformation": { - "label": "Unless otherwise stated, all content on this site is under", - "link": { - "label": "licence MIT", - "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" - } + "externalLinks": [ + { + "label": "Github", + "href": "https://github.com/suitenumerique/docs/" + }, + { + "label": "DINUM", + "href": "https://www.numerique.gouv.fr/dinum/" + }, + { + "label": "ZenDiS", + "href": "https://zendis.de/" + }, + { + "label": "BlockNote.js", + "href": "https://www.blocknotejs.org/" } - }, - "fr": { - "legalLinks": [ - { - "label": "Mentions légales", - "href": "#" - }, - { - "label": "Données personnelles et cookies", - "href": "#" - }, - { - "label": "Accessibilité", - "href": "#" - } - ], - "bottomInformation": { - "label": "Sauf mention contraire, tout le contenu de ce site est sous", - "link": { - "label": "licence MIT", - "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" - } + ], + "bottomInformation": { + "label": "Unless otherwise stated, all content on this site is under", + "link": { + "label": "licence etalab-2.0", + "href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md" } - }, - "de": { - "legalLinks": [ - { - "label": "Impressum", - "href": "#" - }, - { - "label": "Personenbezogene Daten und Cookies", - "href": "#" - }, - { - "label": "Barrierefreiheit", - "href": "#" - } - ], - "bottomInformation": { - "label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter", - "link": { - "label": "licence MIT", - "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" - } + } + }, + "en": { + "legalLinks": [ + { + "label": "Legal Notice", + "href": "#" + }, + { + "label": "Personal data and cookies", + "href": "#" + }, + { + "label": "Accessibility", + "href": "#" } - }, - "nl": { - "legalLinks": [ - { - "label": "Wettelijke bepalingen", - "href": "#" - }, - { - "label": "Persoonlijke gegevens en cookies", - "href": "#" - }, - { - "label": "Toegankelijkheid", - "href": "#" - } - ], - "bottomInformation": { - "label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder", - "link": { - "label": "licence MIT", - "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" - } + ], + "bottomInformation": { + "label": "Unless otherwise stated, all content on this site is under", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" + } + } + }, + "fr": { + "legalLinks": [ + { + "label": "Mentions légales", + "href": "#" + }, + { + "label": "Données personnelles et cookies", + "href": "#" + }, + { + "label": "Accessibilité", + "href": "#" + } + ], + "bottomInformation": { + "label": "Sauf mention contraire, tout le contenu de ce site est sous", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" + } + } + }, + "de": { + "legalLinks": [ + { + "label": "Impressum", + "href": "#" + }, + { + "label": "Personenbezogene Daten und Cookies", + "href": "#" + }, + { + "label": "Barrierefreiheit", + "href": "#" + } + ], + "bottomInformation": { + "label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" + } + } + }, + "nl": { + "legalLinks": [ + { + "label": "Wettelijke bepalingen", + "href": "#" + }, + { + "label": "Persoonlijke gegevens en cookies", + "href": "#" + }, + { + "label": "Toegankelijkheid", + "href": "#" + } + ], + "bottomInformation": { + "label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" } } } } - \ No newline at end of file +} diff --git a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts index 276e06dc..28379315 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts @@ -159,8 +159,8 @@ test.describe('Config: Not loggued', () => { const jsonResponse = await response.json(); expect(jsonResponse.FRONTEND_THEME).toStrictEqual('default'); - const footer = page.locator('footer').first(); + const header = page.locator('header').first(); // alt 'Gouvernement Logo' comes from the theme - await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible(); + await expect(header.getByAltText('Gouvernement Logo')).toBeVisible(); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts index a1878ccb..93405911 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts @@ -1,14 +1,115 @@ import { expect, test } from '@playwright/test'; +import { overrideConfig } from './common'; + test.describe('Footer', () => { test.use({ storageState: { cookies: [], origins: [] } }); + test('checks the footer is not displayed if no config', async ({ page }) => { + await overrideConfig(page, { + theme_customization: {}, + }); + + await page.goto('/'); + await expect(page.locator('footer')).toBeHidden(); + }); + test('checks all the elements are visible', async ({ page }) => { await page.goto('/'); const footer = page.locator('footer').first(); + await expect(footer.getByAltText('Docs Logo')).toBeVisible(); + await expect(footer.getByRole('heading', { name: 'Docs' })).toBeVisible(); + await expect(footer.getByText('BETA')).toBeVisible(); + + await expect(footer.getByRole('link', { name: 'Github' })).toBeVisible(); + await expect(footer.getByRole('link', { name: 'DINUM' })).toBeVisible(); + await expect(footer.getByRole('link', { name: 'ZenDiS' })).toBeVisible(); + + await expect( + footer.getByRole('link', { name: 'BlockNote.js' }), + ).toBeVisible(); + await expect( + footer.getByRole('link', { name: 'Legal Notice' }), + ).toBeVisible(); + await expect( + footer.getByRole('link', { name: 'Personal data and cookies' }), + ).toBeVisible(); + await expect( + footer.getByRole('link', { name: 'Accessibility' }), + ).toBeVisible(); + + await expect( + footer.getByText( + 'Unless otherwise stated, all content on this site is under licence', + ), + ).toBeVisible(); + + // Check the translation + const header = page.locator('header').first(); + await header.getByRole('button').getByText('English').click(); + await page.getByLabel('Français').click(); + + await expect( + page.locator('footer').getByText('Mentions légales'), + ).toBeVisible(); + }); + + test('checks the footer is correctly overrided', async ({ page }) => { + await overrideConfig(page, { + theme_customization: { + footer: { + default: { + logo: { + src: '/assets/logo-gouv.svg', + width: '220px', + alt: 'Gouvernement Logo', + }, + externalLinks: [ + { + label: 'legifrance.gouv.fr', + href: '#', + }, + { + label: 'info.gouv.fr', + href: '#', + }, + ], + legalLinks: [ + { + label: 'Legal link', + href: '#', + }, + ], + bottomInformation: { + label: 'Some bottom information text', + link: { + label: 'a custom label', + href: '#', + }, + }, + }, + fr: { + bottomInformation: { + label: "Text d'information en bas de page en français", + link: { + label: 'un label personnalisé', + href: '#', + }, + }, + }, + }, + }, + }); + + await page.goto('/'); + const footer = page.locator('footer').first(); + await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible(); + await expect(footer.getByRole('heading', { name: 'Docs' })).toBeHidden(); + await expect(footer.getByText('BETA')).toBeHidden(); + await expect( footer.getByRole('link', { name: 'legifrance.gouv.fr' }), ).toBeVisible(); @@ -18,53 +119,30 @@ test.describe('Footer', () => { ).toBeVisible(); await expect( - footer.getByRole('link', { name: 'service-public.fr' }), + footer.getByRole('link', { name: 'Legal link' }), ).toBeVisible(); await expect( - footer.getByRole('link', { name: 'data.gouv.fr' }), + footer.getByText('Some bottom information text'), ).toBeVisible(); await expect( - footer.getByRole('link', { name: 'Legal Notice' }), + footer.getByRole('link', { name: 'a custom label' }), + ).toBeVisible(); + + // Check the translation + const header = page.locator('header').first(); + await header.getByRole('button').getByText('English').click(); + await page.getByLabel('Français').click(); + + await expect( + page + .locator('footer') + .getByText("Text d'information en bas de page en français"), ).toBeVisible(); await expect( - footer.getByRole('link', { name: 'Personal data and cookies' }), - ).toBeVisible(); - - await expect( - footer.getByRole('link', { name: 'Accessibility' }), - ).toBeVisible(); - - await expect( - footer.getByText( - 'Unless otherwise stated, all content on this site is under licence', - ), + footer.getByRole('link', { name: 'un label personnalisé' }), ).toBeVisible(); }); - - const legalPages = [ - { name: 'Legal Notice', url: '/legal-notice/' }, - { name: 'Personal data and cookies', url: '/personal-data-cookies/' }, - { name: 'Accessibility', url: '/accessibility/' }, - ]; - for (const { name, url } of legalPages) { - test(`checks ${name} page`, async ({ page }) => { - await page.goto('/'); - - const footer = page.locator('footer').first(); - await footer.getByRole('link', { name }).click(); - - await expect( - page - .getByRole('heading', { - name, - }) - .first(), - ).toBeVisible(); - - await expect(page).toHaveURL(url); - }); - } }); diff --git a/src/frontend/apps/impress/public/assets/icon-docs.svg b/src/frontend/apps/impress/public/assets/icon-docs.svg new file mode 100644 index 00000000..05cf0436 --- /dev/null +++ b/src/frontend/apps/impress/public/assets/icon-docs.svg @@ -0,0 +1,12 @@ + + + + diff --git a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx index fdfe8c97..306cf100 100644 --- a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx +++ b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx @@ -2,8 +2,13 @@ import { useQuery } from '@tanstack/react-query'; import { APIError, errorCauses, fetchAPI } from '@/api'; import { Theme } from '@/cunningham/'; +import { FooterType } from '@/features/footer'; import { PostHogConf } from '@/services'; +interface ThemeCustomization { + footer?: FooterType; +} + interface ConfigResponse { AI_FEATURE_ENABLED?: boolean; COLLABORATION_WS_URL?: string; @@ -17,6 +22,7 @@ interface ConfigResponse { MEDIA_BASE_URL?: string; POSTHOG_KEY?: PostHogConf; SENTRY_DSN?: string; + theme_customization?: ThemeCustomization; } const LOCAL_STORAGE_KEY = 'docs_config'; diff --git a/src/frontend/apps/impress/src/features/footer/Footer.tsx b/src/frontend/apps/impress/src/features/footer/Footer.tsx index 92f249e2..c6a81c73 100644 --- a/src/frontend/apps/impress/src/features/footer/Footer.tsx +++ b/src/frontend/apps/impress/src/features/footer/Footer.tsx @@ -1,11 +1,15 @@ import Image from 'next/image'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import { Box, StyledLink, Text } from '@/components/'; -import { useCunninghamTheme } from '@/cunningham'; +import { useConfig } from '@/core/config'; + +import { Title } from '../header'; import IconLink from './assets/external-link.svg'; +import { ContentType } from './types'; const BlueStripe = styled.div` position: absolute; @@ -16,9 +20,40 @@ const BlueStripe = styled.div` `; export const Footer = () => { - const { t } = useTranslation(); - const { themeTokens } = useCunninghamTheme(); - const logo = themeTokens.logo; + const { data: config } = useConfig(); + const footerJson = config?.theme_customization?.footer; + const { i18n, t } = useTranslation(); + const resolvedLanguage = i18n.resolvedLanguage; + const [content, setContent] = useState(); + + useEffect(() => { + if (!footerJson) { + return; + } + + const langData = footerJson[resolvedLanguage as keyof typeof footerJson]; + const innerContent: ContentType = {}; + + innerContent.logo = langData?.logo || footerJson?.default?.logo; + innerContent.legalLinks = + langData?.legalLinks || footerJson?.default?.legalLinks; + innerContent.externalLinks = + langData && 'externalLinks' in langData + ? langData?.externalLinks + : footerJson?.default?.externalLinks; + innerContent.bottomInformation = + langData && 'bottomInformation' in langData + ? langData?.bottomInformation + : footerJson?.default?.bottomInformation; + + setContent(innerContent); + }, [footerJson, resolvedLanguage]); + + const { logo, legalLinks, externalLinks, bottomInformation } = content || {}; + + if (!footerJson || (!legalLinks && !externalLinks && !bottomInformation)) { + return null; + } return ( @@ -34,14 +69,29 @@ export const Footer = () => { {logo && ( - {logo.alt} + + {logo?.src && ( + {logo?.alt + )} + {logo.withTitle && ( + + + </Box> + )} + </Box> )} </Box> </Box> @@ -54,40 +104,24 @@ export const Footer = () => { `} className="--docs--footer-external-links" > - {[ - { - label: 'legifrance.gouv.fr', - href: 'https://legifrance.gouv.fr/', - }, - { - label: 'info.gouv.fr', - href: 'https://info.gouv.fr/', - }, - { - label: 'service-public.fr', - href: 'https://service-public.fr/', - }, - { - label: 'data.gouv.fr', - href: 'https://data.gouv.fr/', - }, - ].map(({ label, href }) => ( - <StyledLink - key={label} - href={href} - target="__blank" - $css={` - gap:0.2rem; - transition: box-shadow 0.3s; - &:hover { - box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text); - } - `} - > - <Text $weight="bold">{label}</Text> - <IconLink width={18} /> - </StyledLink> - ))} + {externalLinks && + externalLinks.map(({ label, href }) => ( + <StyledLink + key={label} + href={href} + target="__blank" + $css={` + gap:0.2rem; + transition: box-shadow 0.3s; + &:hover { + box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text); + } + `} + > + <Text $weight="bold">{label}</Text> + <IconLink width={18} /> + </StyledLink> + ))} </Box> </Box> <Box @@ -102,66 +136,62 @@ export const Footer = () => { `} className="--docs--footer-internal-links" > - {[ - { - label: t('Legal Notice'), - href: '/legal-notice', - }, - { - label: t('Personal data and cookies'), - href: '/personal-data-cookies', - }, - { - label: t('Accessibility'), - href: '/accessibility', - }, - ].map(({ label, href }) => ( - <StyledLink - key={label} - href={href} - $css={` - padding-right: 1rem; - &:not(:last-child) { - box-shadow: inset -1px 0px 0px 0px var(--c--theme--colors--greyscale-200); - } - `} - > - <Text - $variation="600" - $size="m" - $transition="box-shadow 0.3s" - $css={` - &:hover { - box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text); + {legalLinks && + legalLinks.map(({ label, href }) => ( + <StyledLink + key={label} + href={href} + $css={css` + padding-right: 1rem; + &:not(:last-child) { + box-shadow: inset -1px 0px 0px 0px + var(--c--theme--colors--greyscale-200); } `} > - {label} - </Text> - </StyledLink> - ))} + <Text + $variation="600" + $size="m" + $transition="box-shadow 0.3s" + $css={css` + &:hover { + box-shadow: 0px 2px 0 0 + var(--c--theme--colors--greyscale-text); + } + `} + > + {label} + </Text> + </StyledLink> + ))} </Box> - <Text - as="p" - $size="m" - $margin={{ top: 'big' }} - $variation="600" - $display="inline" - className="--docs--footer-licence" - > - {t('Unless otherwise stated, all content on this site is under')}{' '} - <StyledLink - href="https://github.com/etalab/licence-ouverte/blob/master/LO.md" - target="__blank" - $css={` - display:inline-flex; - box-shadow: 0px 1px 0 0 var(--c--theme--colors--greyscale-text); - `} + {bottomInformation && ( + <Text + as="p" + $size="m" + $margin={{ top: 'big' }} + $variation="600" + $display="inline" + className="--docs--footer-licence" > - <Text $variation="600">licence etalab-2.0</Text> - <IconLink width={18} /> - </StyledLink> - </Text> + {bottomInformation.label}{' '} + {bottomInformation.link && ( + <StyledLink + href={bottomInformation.link.href} + target="__blank" + $css={css` + display: inline-flex; + box-shadow: 0px 1px 0 0 + var(--c--theme--colors--greyscale-text); + gap: 0.2rem; + `} + > + <Text $variation="600">{bottomInformation.link.label}</Text> + <IconLink width={14} /> + </StyledLink> + )} + </Text> + )} </Box> </Box> ); diff --git a/src/frontend/apps/impress/src/features/footer/index.tsx b/src/frontend/apps/impress/src/features/footer/index.tsx index ddcc5a9c..166c3c20 100644 --- a/src/frontend/apps/impress/src/features/footer/index.tsx +++ b/src/frontend/apps/impress/src/features/footer/index.tsx @@ -1 +1,2 @@ export * from './Footer'; +export * from './types'; diff --git a/src/frontend/apps/impress/src/features/footer/types.ts b/src/frontend/apps/impress/src/features/footer/types.ts new file mode 100644 index 00000000..10320f18 --- /dev/null +++ b/src/frontend/apps/impress/src/features/footer/types.ts @@ -0,0 +1,28 @@ +export interface FooterType { + default: ContentType; + [key: string]: ContentType; +} + +export interface BottomInformation { + label: string; + link?: Link; +} + +export interface Link { + label: string; + href: string; +} + +export interface Logo { + src: string; + width: string; + alt: string; + withTitle: boolean; +} + +export interface ContentType { + logo?: Logo; + externalLinks?: Link[]; + legalLinks?: Link[]; + bottomInformation?: BottomInformation; +} diff --git a/src/helm/env.d/dev/configuration/theme/demo.json b/src/helm/env.d/dev/configuration/theme/demo.json index aeefb536..29200ada 100644 --- a/src/helm/env.d/dev/configuration/theme/demo.json +++ b/src/helm/env.d/dev/configuration/theme/demo.json @@ -1,6 +1,12 @@ { "footer": { "default": { + "logo": { + "src": "/assets/icon-docs.svg", + "width": "54px", + "alt": "Docs Logo", + "withTitle": true + }, "externalLinks": [ { "label": "Github",