diff --git a/docs/.gitignore b/docs/.gitignore index 5df74e35b94..581eb8a84e0 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -22,3 +22,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* .log/ +.vercel diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 1a463ce7005..c26be9db2de 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -34,6 +34,11 @@ const config = { defer: true, "data-domain": "hydra.family", }, + { + // GDPR + src: "https://cmp.osano.com/AzZXI3TYiFWNB5yus/09f536e2-8feb-4b15-8f96-249e7ae491a3/osano.js", + async: false, + }, ], presets: [ @@ -146,6 +151,13 @@ const config = { ], }, ], + [ + "@docusaurus/plugin-google-gtag", + { + trackingID: "G-Z847J5GYDW", + anonymizeIP: true, + }, + ], ], themeConfig: diff --git a/docs/src/analytics/AnalyticsContext.tsx b/docs/src/analytics/AnalyticsContext.tsx new file mode 100644 index 00000000000..9f0134ddb35 --- /dev/null +++ b/docs/src/analytics/AnalyticsContext.tsx @@ -0,0 +1,65 @@ +import React, { + PropsWithChildren, + createContext, + useCallback, + useEffect, +} from "react"; +import dayjs from "dayjs"; +import TrackRoute from "./TrackRoute"; +import useHasConsent, { ConsentType } from "./useHasConsent"; + +export const AnalyticsContext = createContext< + (eventName: string, eventProps?: Record) => void +>(() => {}); + +export function AnalyticsProvider({ children }: PropsWithChildren) { + const analyticsAccepted = useHasConsent(ConsentType.ANALYTICS); + + const gtag = (...args: any[]) => { + if (typeof window !== "undefined" && (window as any).gtag) { + (window as any).gtag(...args); + } + }; + + const capture = useCallback( + (eventName: string, eventProperties: Record = {}) => { + if (analyticsAccepted) { + gtag("event", eventName, { + sent_at_local: dayjs().format(), + ...eventProperties, + }); + } + }, + [analyticsAccepted] + ); + + useEffect(() => { + const gaMeasurementId = "G-Z847J5GYDW"; // Replace with env variagble + if (analyticsAccepted) { + if (!(window as any).gtag) { + const script = document.createElement("script"); + script.async = true; + script.src = `https://www.googletagmanager.com/gtag/js?id=${gaMeasurementId}`; + document.head.appendChild(script); + + // Initialize dataLayer and gtag + (window as any).dataLayer = (window as any).dataLayer || []; + (window as any).gtag = function gtag(...args: any[]) { + (window as any).dataLayer.push(args); + }; + gtag("js", new Date()); + gtag("config", gaMeasurementId); + } + } else { + // Disable Google Analytics if consent is revoked + gtag("config", gaMeasurementId, { send_page_view: false }); + } + }, [analyticsAccepted]); + + return ( + + + {children} + + ); +} diff --git a/docs/src/analytics/TrackRoute.tsx b/docs/src/analytics/TrackRoute.tsx new file mode 100644 index 00000000000..db9b2faea24 --- /dev/null +++ b/docs/src/analytics/TrackRoute.tsx @@ -0,0 +1,17 @@ +import { useLocation } from "@docusaurus/router"; +import React, { useContext, useEffect } from "react"; +import { AnalyticsContext } from "./AnalyticsContext"; + +export default function TrackRoute() { + const location = useLocation(); + const capture = useContext(AnalyticsContext); + + useEffect(() => { + capture("page_view", { + page_path: location.pathname, + page_search: location.search, + }); + }, [capture, location.pathname, location.search]); + + return null; +} diff --git a/docs/src/analytics/osano.ts b/docs/src/analytics/osano.ts new file mode 100644 index 00000000000..2cbe0bc15d0 --- /dev/null +++ b/docs/src/analytics/osano.ts @@ -0,0 +1,41 @@ +export enum OsanoConsentType { + ESSENTIAL = "ESSENTIAL", + STORAGE = "STORAGE", + MARKETING = "MARKETING", + PERSONALIZATION = "PERSONALIZATION", + ANALYTICS = "ANALYTICS", + OPT_OUT = "OPT_OUT" +} + +export enum OsanoConsentDecision { + ACCEPT = "ACCEPT", + DENY = "DENY" +} + +export type OsanoConsentMap = Record; + +export enum OsanoEvent { + CONSENT_SAVED = "osano-cm-consent-saved" +} + +export type OsanoEventCallback = { + [OsanoEvent.CONSENT_SAVED]: (changed: Partial) => void; +}; + +declare global { + interface Osano { + cm?: { + getConsent(): OsanoConsentMap; + addEventListener: ( + ev: T, + callback: OsanoEventCallback[T] + ) => void; + removeEventListener: ( + ev: T, + callback: OsanoEventCallback[T] + ) => void; + }; + } + + var Osano: Osano | undefined; +} diff --git a/docs/src/analytics/useHasConsent.tsx b/docs/src/analytics/useHasConsent.tsx new file mode 100644 index 00000000000..46839c2ab94 --- /dev/null +++ b/docs/src/analytics/useHasConsent.tsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; +import { + OsanoConsentDecision, + OsanoConsentType, + OsanoEvent, + OsanoEventCallback +} from "./osano"; + +export { OsanoConsentType as ConsentType }; + +export default function useHasConsent( + type: OsanoConsentType +): boolean | undefined { + const initialConsent = + typeof Osano !== "undefined" + ? Osano.cm?.getConsent()[type] === OsanoConsentDecision.ACCEPT + : undefined; + + const [state, setState] = useState(initialConsent); + + useEffect(() => { + const cm = typeof Osano !== "undefined" ? Osano.cm : undefined; + + if (!cm) { + return; + } + + setState(cm.getConsent()[type] === OsanoConsentDecision.ACCEPT); + + const handler: OsanoEventCallback[OsanoEvent.CONSENT_SAVED] = (changed) => { + if (type in changed) { + setState(changed[type] === OsanoConsentDecision.ACCEPT); + } + }; + + cm.addEventListener(OsanoEvent.CONSENT_SAVED, handler); + return () => { + cm.removeEventListener(OsanoEvent.CONSENT_SAVED, handler); + }; + }, [type]); + + return state; +} diff --git a/docs/src/css/osano.css b/docs/src/css/osano.css new file mode 100644 index 00000000000..17ef7d677f1 --- /dev/null +++ b/docs/src/css/osano.css @@ -0,0 +1,398 @@ +:root { + --osano-white: #ffffff; + --osano-black: #121326; + --osano-primary: #6875e8; + --osano-dialog-max-width: 960px; + --osano-dialog-elastic-padding: calc( + (100% - min(100vw, var(--osano-dialog-max-width))) / 2 + ); + --osano-dialog-magic-padding: max(var(--osano-dialog-elastic-padding), 20px); + --osano-close-color: var(--osano-black); + --osano-link-color: var(--osano-black); + --osano-disclosure-color: var(--osano-black); + --osano-font-family: var(--default-font-family); + --osano-font-color: var(--osano-black); + --osano-background-color: var(--osano-white); + --osano-border-radius: 0; + --osano-btn-font-variant: normal; + --osano-btn-text-transform: uppercase; + --osano-btn-letter-spacing: normal; + --osano-btn-border-radius: 100px; + --osano-btn-color: var(--osano-white); + --osano-btn-bg-color: var(--osano-primary); + --osano-btn-hover-color: var(--osano-primary); + --osano-btn-bg-hover-color: var(--osano-white); + --osano-switch-color: #e0e0e0; + --osano-switch-active-color: var(--osano-primary); + --osano-border-gradient: linear-gradient( + 94.22deg, + var(--osano-black) -18.3%, + var(--osano-black) 118.89% + ); + --osano-btn-border-gradient: linear-gradient( + 94.22deg, + var(--osano-primary) -18.3%, + var(--osano-primary) 118.89% + ); + --osano-background-gradient: linear-gradient( + var(--osano-background-color), + var(--osano-background-color) + ) + padding-box, + var(--osano-border-gradient) border-box; + --osano-btn-bg-gradient: linear-gradient( + var(--osano-btn-bg-color), + var(--osano-btn-bg-color) + ) + padding-box, + var(--osano-btn-border-gradient) border-box; +} + +.osano-cm-window { + font-family: var(--osano-font-family); + font-size: 16px; +} + +/** + * Initial Dialog + * -------------- + * + * 1. Make font color consistent + * 2. Override background for gradient border effect + * - We can't use 'max-width: xxx; margin: auto' to center + * 3. Align to center + * 4. Make mobile whitespace consistent + * 5. Make desktop whitespace consistent + */ +.osano-cm-dialog { + color: var(--osano-font-color); /* 1 */ + border-radius: var(--osano-border-radius, 16px); /* 2 */ + border: 2px solid transparent; /* 2 */ + background: var(--osano-background-gradient); /* 2 */ + margin-left: var(--osano-dialog-magic-padding); /* 3 */ + margin-right: var(--osano-dialog-magic-padding); /* 3 */ + padding: 30px; /* 4 */ + margin-bottom: 20px; /* 4 */ + line-height: 1.625; +} + +@media screen and (min-width: 768px) { + .osano-cm-dialog { + padding: 60px; /* 5 */ + margin-bottom: 40px; /* 5 */ + } +} + +/** + * Button + * ------ + * + * 1. Make text consistent + * 2. Gradient border effect + * 3. Align size with site design + */ +.osano-cm-button { + font-weight: 600; + font-family: var(--osano-font-family); /* 1 */ + font-variant: var(--osano-btn-font-variant, normal); + text-transform: var(--osano-btn-text-transform, none); + letter-spacing: var(--osano-btn-letter-spacing, normal); + color: var(--osano-btn-color); /* 1 */ + background-color: var(--osano-btn-bg-color); /* 2 */ + border: 2px solid transparent; /* 2 */ + border-radius: var(--osano-btn-border-radius, 14px); /* 2 */ + background: var(--osano-btn-bg-gradient); /* 2 */ + height: 44px; /* 3 */ + transition: color 0.4s ease-out, background-color 0.4s ease-out; /* 4 */ +} + +.osano-cm-button:hover:not(:disabled):not(:active):not([aria-selected="true"]) { + transform: scale(1.035); /* 4 */ + box-shadow: 0px 4px 10px rgba(167, 143, 160, 0.2); +} + +/* Button: focus/hover */ +.osano-cm-button:focus, +.osano-cm-button:hover { + color: var(--osano-btn-hover-color); + background: var(--osano-btn-bg-hover-color); + border-color: var(--osano-btn-hover-color); +} +/* Buttons in group */ +.osano-cm-dialog--type_bar .osano-cm-button { + width: 100%; +} +/** + * When buttons are in a stack... + * 1. Make mobile whitespace consistent + * 2. Make desktop whitespace consistent + */ +.osano-cm-dialog--type_bar .osano-cm-button + .osano-cm-button { + margin-top: calc(10px - 0.25em); /* 1 */ + @media screen and (min-width: 768px) { + margin-top: calc(20px - 0.25em); /* 2 */ + } +} +/** + * 1. Remove user-agent defaults for link color + */ +.osano-cm-link, +.osano-cm-link:hover { + color: var(--osano-link-color); /* 1 */ + font-weight: bold; +} +/** + * Toggle Switch + * ------------- + * 1. Align colors + */ +.osano-cm-toggle__switch { + background-color: var(--osano-switch-color); /* 1 */ +} +/* Toggle switch: focus/hover */ +.osano-cm-toggle__input:focus + .osano-cm-toggle__switch, +.osano-cm-toggle__input:hover + .osano-cm-toggle__switch { + background-color: var(--osano-switch-color); /* 1 */ + border-color: var(--osano-switch-color); /* 1 */ +} +/* Toggle switch: checked: focus/hover */ +.osano-cm-toggle__input:checked:focus + .osano-cm-toggle__switch, +.osano-cm-toggle__input:checked:hover + .osano-cm-toggle__switch, +.osano-cm-toggle__input:checked + .osano-cm-toggle__switch { + background-color: var(--osano-switch-active-color); /* 1 */ + border-color: var(--osano-switch-active-color); /* 1 */ +} + +.osano-cm-toggle__input:checked:focus + .osano-cm-toggle__switch:after, +.osano-cm-toggle__input:checked:hover + .osano-cm-toggle__switch:after, +.osano-cm-toggle__input:checked + .osano-cm-toggle__switch:after { + background-color: #fff; /* 1 */ + border-color: var(--osano-btn-bg-color); /* 1 */ +} + +.osano-cm-toggle__input:disabled + .osano-cm-toggle__switch, +.osano-cm-toggle__input:disabled:focus + .osano-cm-toggle__switch, +.osano-cm-toggle__input:disabled:hover + .osano-cm-toggle__switch { + background-color: var(--osano-switch-disabled-color); /* 1 */ + border-color: var(--osano-switch-disabled-color); /* 1 */ +} +/** + * View cookies dropdown + * --------------------- + */ +.osano-cm-disclosure__toggle, +.osano-cm-disclosure__toggle:hover { + color: inherit; /* 1 */ +} +/** + * 1. Fix horrible info-dialog shadow + * 2. Consistent font color + */ +.osano-cm-info { + box-shadow: 0 0 12px 6px rgba(0, 0, 0, 0.15); /* 1. */ + color: var(--osano-font-color); /* 2 */ +} +/* 1. Set correct font family on toggle labels */ +.osano-cm-drawer-toggle { + font-family: var(--osano-btn-font-family); /* 1 */ +} +/** + * 1. Hide Osano logo + */ +.osano-cm-view__powered-by { + display: none; /* 1 */ +} +/** + * Sidebar styling: + * ---------------- + */ +.osano-cm-info-dialog__info { +} + +/** + * 1. Make wider on desktop + */ +@media screen and (min-width: 768px) { + .osano-cm-info-dialog__info { + max-width: 465px; /* 1 */ + } +} + +/** + * 1. Remove sticky behaviour + * 2. Adjust desktop close button position based on whitespace change + */ +.osano-cm-info-dialog-header { + position: relative; /* 1 */ +} + +@media screen and (min-width: 768px) { + .osano-cm-close { + margin-top: -30px; /* 2 */ + } +} + +/** + * 1. Make sidebar title bold + * 2. Make desktop padding consistent with content + */ +.osano-cm-info-dialog-header__header { + font-weight: bold; /* 1 */ +} + +@media screen and (min-width: 768px) { + .osano-cm-info-dialog-header__header { + padding-top: 60px; /* 2 */ + padding-left: 60px; /* 2 */ + } +} + +.osano-cm-info-dialog-header__close:focus { + stroke: var(--osano-close-color); + background: var(--osano-background-color); + border: none; +} + +/** + * 1. Make text more readable + */ +.osano-cm-description { + font-size: 14px; /* 1 */ + line-height: 1.64; /* 1 */ +} + +/** + * 1. Add whitespace to bottom of dialog + */ +.osano-cm-save.osano-cm-view__button { + margin-bottom: 60px; /* 1 */ +} + +/** + * 1. Make mobile whitespace consistent + * 2. Make desktop whitespace consistent + */ +.osano-cm-view--type_consent { + padding: 0 20px; /* 1 */ +} + +@media screen and (min-width: 768px) { + .osano-cm-view--type_consent { + padding: 0 60px; /* 2 */ + } +} + +/** + * 1. Make section headings bold + */ +.osano-cm-toggle__label[role="heading"] { + font-weight: bold; /* 1 */ +} + +/** + * 1. Make whitespace consistent with parent + */ +.osano-cm-disclosure { + padding-left: 0; /* 1 */ + padding-right: 0; /* 1 */ + margin-left: 0; /* 1 */ + margin-right: 0; /* 1 */ + margin-bottom: 30px; /* 1 */ +} + +/** + * 1. Make font-size consistent + * 2. Add preceeding dropdown arrow + * 3. Make font color consistent + */ +.osano-cm-disclosure__toggle, +.osano-cm-disclosure__toggle:hover { + font-size: 14px; /* 1 */ + position: relative; /* 2 */ + color: var(--osano-disclosure-color); /* 3 */ + text-decoration: none; +} + +.osano-cm-disclosure__toggle:before { + display: inline-block; /* 2 */ + content: ""; /* 2 */ + position: relative; /* 2 */ + width: 12.5px; /* 2 */ + height: 8.5px; /* 2 */ + margin-right: 8px; /* 2 */ + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxMCIgZmlsbD0ibm9uZSI+PHBhdGggc3Ryb2tlPSIjMTIxMzI2IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im0yIDEuNSA1IDYgNS02Ii8+PC9zdmc+") + center center no-repeat; /* 2 */ +} + +.osano-cm-disclosure__toggle[aria-expanded="true"]:before { + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxMCIgZmlsbD0ibm9uZSI+PHBhdGggc3Ryb2tlPSIjMTIxMzI2IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im0xMiA4LjUtNS02LTUgNiIvPjwvc3ZnPg=="); +} + +/** + * 1. Override cookie widget icon with custom SVG + */ +.osano-cm-window__widget { + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgZmlsbD0ibm9uZSI+PGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMjAiIGZpbGw9IiMwMDAiLz48cGF0aCBmaWxsPSIjQ0NDIiBkPSJNMTcuNjA1IDE2Ljk0NWEyLjM4IDIuMzggMCAwIDAgMS43NTUtLjcyNWMuNDgtLjQ4My43Mi0xLjA3LjcyLTEuNzZhMi4zOCAyLjM4IDAgMCAwLS43MjUtMS43NTUgMi40MDQgMi40MDQgMCAwIDAtMS43Ni0uNzIgMi4zOCAyLjM4IDAgMCAwLTEuNzU1LjcyNWMtLjQ4LjQ4My0uNzIgMS4wNy0uNzIgMS43NnMuMjQyIDEuMjc1LjcyNSAxLjc1NSAxLjA3LjcyIDEuNzYuNzJabS0zLjIgOGEyLjM4IDIuMzggMCAwIDAgMS43NTUtLjcyNWMuNDgtLjQ4My43Mi0xLjA3LjcyLTEuNzZhMi4zOCAyLjM4IDAgMCAwLS43MjUtMS43NTUgMi40MDQgMi40MDQgMCAwIDAtMS43Ni0uNzIgMi4zOCAyLjM4IDAgMCAwLTEuNzU1LjcyNWMtLjQ4LjQ4My0uNzIgMS4wNy0uNzIgMS43NnMuMjQyIDEuMjc1LjcyNSAxLjc1NSAxLjA3LjcyIDEuNzYuNzJabTEwLjM5NSAxLjUyYy40NTMgMCAuODMzLS4xNTMgMS4xNC0uNDYuMzA3LS4zMDcuNDYtLjY4Ny40Ni0xLjE0IDAtLjQ1My0uMTUzLS44MzMtLjQ2LTEuMTRhMS41NDkgMS41NDkgMCAwIDAtMS4xNC0uNDZjLS40NTMgMC0uODMzLjE1My0xLjE0LjQ2LS4zMDcuMzA3LS40Ni42ODctLjQ2IDEuMTQgMCAuNDUzLjE1My44MzMuNDYgMS4xNC4zMDcuMzA3LjY4Ny40NiAxLjE0LjQ2Wm0tNC43OSA5LjZjLTIuMTk0IDAtNC4yNjMtLjQyLTYuMjEtMS4yNmExNi4yMjcgMTYuMjI3IDAgMCAxLTUuMS0zLjQ0IDE2LjIyOCAxNi4yMjggMCAwIDEtMy40NC01LjFDNC40MiAyNC4zMTggNCAyMi4yNTIgNCAyMC4wNjVjMC0yLjQ1My41Mi00Ljc0NyAxLjU2LTYuODhzMi40MzMtMy45NCA0LjE4LTUuNDJjMS43NDctMS40OCAzLjc2LTIuNTQ3IDYuMDQtMy4yIDIuMjgtLjY1MyA0LjY0Ny0uNzQgNy4xLS4yNi0uMTYgMS4yLS4wNTMgMi4zMzMuMzIgMy40LjM3MyAxLjA2Ny45NCAxLjk3MyAxLjcgMi43MmE2Ljg4IDYuODggMCAwIDAgMi43NCAxLjY0YzEuMDY3LjM0NyAyLjE4Ny40MjcgMy4zNi4yNC0uNTMzIDEuNjI3LS4yNCAzLjA4Ny44OCA0LjM4IDEuMTIgMS4yOTMgMi40OCAyLjAwNyA0LjA4IDIuMTQuMjEzIDIuMzItLjA2IDQuNTItLjgyIDYuNmExNS45OTQgMTUuOTk0IDAgMCAxLTMuMzQgNS40OGMtMS40NjcgMS41NzMtMy4yMSAyLjgyNy01LjIzMiAzLjc2LTIuMDIuOTMzLTQuMjA3IDEuNC02LjU1OSAxLjRabS0uMDEtMi40YzMuNzg3IDAgNi45MzMtMS4yNDcgOS40NC0zLjc0IDIuNTA3LTIuNDkzIDMuOTA3LTUuNTY3IDQuMi05LjIyLTEuNDQtLjUzMy0yLjYwNy0xLjMyNy0zLjUtMi4zOGE3LjIyNyA3LjIyNyAwIDAgMS0xLjY2LTMuNjJjLTIuMTYtLjI5My0zLjk4LTEuMjI3LTUuNDYtMi44LTEuNDgtMS41NzMtMi4zMjctMy40LTIuNTQtNS40OC0xLjk3My0uMDgtMy44Mi4yNC01LjU0Ljk2LTEuNzIuNzItMy4yMTMgMS43MDctNC40OCAyLjk2YTEzLjY3OCAxMy42NzggMCAwIDAtMi45OCA0LjQyIDEzLjQxIDEzLjQxIDAgMCAwLTEuMDggNS4zYzAgMy43ODcgMS4zMiA3IDMuOTYgOS42NCAyLjY0IDIuNjQgNS44NTMgMy45NiA5LjY0IDMuOTZaIi8+PC9zdmc+") + center center no-repeat; /* 1 */ + background-size: 40px; /* 1 */ +} + +.osano-cm-window__widget > svg { + display: none; +} + +/** + * 1. Remove cookie widget icon focus outline + */ +.osano-cm-widget:focus { + outline: none; /* 1 */ +} +/** + * 1. Override default close button colors + */ +.osano-cm-dialog__close { + color: var(--osano-close-color); /* 1 */ + stroke: var(--osano-close-color); /* 1 */ +} + +.osano-cm-disclosure--collapse { + border-bottom: 0; + padding-bottom: 0; +} +.osano-cm-disclosure { + margin: 0; + padding: 0; +} +.osano-cm-toggle { + padding-top: 1.5em; +} +label[for="osano-cm-drawer-toggle--category_MARKETING"] { + display: none; +} +p[id="osano-cm-drawer-toggle--category_MARKETING--description"] { + display: none; +} +div[aria-controls="osano-cm-MARKETING_disclosures"] { + display: none; +} +ul[id="osano-cm-MARKETING_disclosures"] { + display: none; +} +label[for="osano-cm-dialog-toggle--category_MARKETING"] { + display: none; +} +label[for="osano-cm-drawer-toggle--category_PERSONALIZATION"] { + display: none; +} +p[id="osano-cm-drawer-toggle--category_PERSONALIZATION--description"] { + display: none; +} +div[aria-controls="osano-cm-PERSONALIZATION_disclosures"] { + display: none; +} +ul[id="osano-cm-PERSONALIZATION_disclosures"] { + display: none; +} +label[for="osano-cm-dialog-toggle--category_PERSONALIZATION"] { + display: none; +} diff --git a/docs/src/theme/Root.tsx b/docs/src/theme/Root.tsx new file mode 100644 index 00000000000..4213997e0d2 --- /dev/null +++ b/docs/src/theme/Root.tsx @@ -0,0 +1,6 @@ +import React, { PropsWithChildren } from "react"; +import { AnalyticsProvider } from "../analytics/AnalyticsContext"; + +export default function Root({ children }: PropsWithChildren) { + return {children}; +}