From 71318367fb9e3daef77a89c03e52a29c9c2c299b Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:58:40 +0200 Subject: [PATCH] [docs-infra] Move ads to the `@mui/docs` package (#42944) --- docs/nextConfigDocsInfra.js | 2 - docs/package.json | 1 - docs/pages/_app.js | 1 + docs/src/modules/components/Ad.tsx | 246 +----------------- docs/src/modules/components/AdInHouse.tsx | 13 - docs/src/modules/components/ApiPage.js | 3 +- docs/src/modules/components/AppLayoutDocs.js | 14 +- docs/src/modules/components/Demo.js | 2 +- docs/src/modules/components/MarkdownDocs.js | 3 +- docs/src/modules/components/MarkdownDocsV2.js | 3 +- docs/src/modules/utils/loadScript.js | 8 - docs/types/ga.d.ts | 7 - packages/mui-docs/package.json | 4 +- packages/mui-docs/src/Ad/Ad.tsx | 240 +++++++++++++++++ .../mui-docs/src/Ad}/AdCarbon.tsx | 10 +- .../mui-docs/src/Ad}/AdDisplay.tsx | 30 +-- .../mui-docs/src/Ad}/AdGuest.tsx | 12 +- packages/mui-docs/src/Ad/AdInHouse.tsx | 8 + .../mui-docs/src/Ad}/AdManager.tsx | 8 +- packages/mui-docs/src/Ad/AdProvider.tsx | 35 +++ .../mui-docs/src/Ad}/ad.styles.ts | 12 +- packages/mui-docs/src/Ad/index.ts | 7 + .../src/DocsProvider/DocsProvider.tsx | 11 +- packages/mui-docs/src/utils/loadScript.ts | 10 + packages/mui-docs/tsconfig.json | 2 +- pnpm-lock.yaml | 9 +- 26 files changed, 357 insertions(+), 344 deletions(-) delete mode 100644 docs/src/modules/components/AdInHouse.tsx delete mode 100644 docs/src/modules/utils/loadScript.js delete mode 100644 docs/types/ga.d.ts create mode 100644 packages/mui-docs/src/Ad/Ad.tsx rename {docs/src/modules/components => packages/mui-docs/src/Ad}/AdCarbon.tsx (92%) rename {docs/src/modules/components => packages/mui-docs/src/Ad}/AdDisplay.tsx (78%) rename {docs/src/modules/components => packages/mui-docs/src/Ad}/AdGuest.tsx (73%) create mode 100644 packages/mui-docs/src/Ad/AdInHouse.tsx rename {docs/src/modules/components => packages/mui-docs/src/Ad}/AdManager.tsx (82%) create mode 100644 packages/mui-docs/src/Ad/AdProvider.tsx rename {docs/src/modules/components => packages/mui-docs/src/Ad}/ad.styles.ts (86%) create mode 100644 packages/mui-docs/src/Ad/index.ts create mode 100644 packages/mui-docs/src/utils/loadScript.ts diff --git a/docs/nextConfigDocsInfra.js b/docs/nextConfigDocsInfra.js index 38a637a11855a6..b5e7a45f170b9d 100644 --- a/docs/nextConfigDocsInfra.js +++ b/docs/nextConfigDocsInfra.js @@ -71,8 +71,6 @@ function withDocsInfra(nextConfig) { NETLIFY_DEPLOY_URL: process.env.DEPLOY_URL, // Name of the site, its Netlify subdomain; for example, material-ui-docs NETLIFY_SITE_NAME: process.env.SITE_NAME, - // The ratio of ads display reported to Google Analytics. Used to avoid an exceed on the Google Analytics quotas. - GA_ADS_DISPLAY_RATIO: 0.1, }, experimental: { scrollRestoration: true, diff --git a/docs/package.json b/docs/package.json index 9000b806da039a..5da4febdc8544b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -129,7 +129,6 @@ "@types/react-transition-group": "^4.4.10", "@types/react-window": "^1.8.8", "@types/stylis": "^4.2.0", - "@types/gtag.js": "^0.0.20", "chai": "^4.4.1", "cross-fetch": "^4.0.0", "gm": "^1.25.0", diff --git a/docs/pages/_app.js b/docs/pages/_app.js index f3ce4ea59bbca7..c5047f86c1f6fd 100644 --- a/docs/pages/_app.js +++ b/docs/pages/_app.js @@ -329,6 +329,7 @@ function AppWrapper(props) { diff --git a/docs/src/modules/components/Ad.tsx b/docs/src/modules/components/Ad.tsx index b94043f4cfdf69..97743c99229f57 100644 --- a/docs/src/modules/components/Ad.tsx +++ b/docs/src/modules/components/Ad.tsx @@ -1,244 +1,4 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import Typography from '@mui/material/Typography'; -import Box from '@mui/material/Box'; -import Paper from '@mui/material/Paper'; -import AdCarbon from 'docs/src/modules/components/AdCarbon'; -import AdInHouse from 'docs/src/modules/components/AdInHouse'; -import { AdContext, adShape } from 'docs/src/modules/components/AdManager'; -import { useTranslate } from '@mui/docs/i18n'; +// Backwards compatibility for Toolpad. +// TODO: remove when Toolpad migrated to `@mui/docs/i18n` -function PleaseDisableAdblock() { - const t = useTranslate(); - - return ( - - - {t('likeMui')} - - - {t('adblock')} - - - {t('thanks')}{' '} - - ❤️ - - - - ); -} - -const disableAd = - process.env.NODE_ENV !== 'production' && process.env.ENABLE_AD_IN_DEV_MODE !== 'true'; -const inHouseAds = [ - { - name: 'scaffoldhub', - link: 'https://v2.scaffoldhub.io/scaffolds/react-material-ui?partner=1', - img: '/static/ads-in-house/scaffoldhub.png', - description: 'ScaffoldHub. Automate building your full-stack Material UI web-app.', - }, - { - name: 'templates', - link: 'https://mui.com/store/?utm_source=docs&utm_medium=referral&utm_campaign=in-house-templates', - img: '/static/ads-in-house/themes-2.jpg', - description: - 'Premium Templates. Start your project with the best templates for admins, dashboards, and more.', - }, - { - name: 'themes', - link: 'https://mui.com/store/?utm_source=docs&utm_medium=referral&utm_campaign=in-house-themes', - img: '/static/ads-in-house/themes.png', - description: - 'Premium Themes. Kickstart your application development with a ready-made theme.', - }, - { - name: 'tidelift', - link: 'https://tidelift.com/subscription/pkg/npm-material-ui?utm_source=npm-material-ui&utm_medium=referral&utm_campaign=enterprise&utm_content=ad', - img: '/static/ads-in-house/tidelift.png', - description: - 'MUI for enterprise. Save time and reduce risk. Managed open source — backed by maintainers.', - }, - { - name: 'figma', - link: 'https://mui.com/store/items/figma-react/?utm_source=docs&utm_medium=referral&utm_campaign=in-house-figma', - img: '/static/ads-in-house/figma.png', - description: - 'For Figma. A large UI kit with over 600 handcrafted Material UI, MUI X, Joy UI components 🎨.', - }, -]; - -class AdErrorBoundary extends React.Component<{ - eventLabel: string | null; - children?: React.ReactNode | undefined; -}> { - static propTypes = { - children: PropTypes.node.isRequired, - eventLabel: PropTypes.string, - }; - - state = { didError: false }; - - static getDerivedStateFromError() { - return { didError: true }; - } - - componentDidCatch() { - // send explicit `'null'` - const eventLabel = String(this.props.eventLabel); - // TODO: Use proper error monitoring service (for example Sentry) instead - - window.gtag('event', 'ad', { - eventAction: 'crash', - eventLabel, - }); - } - - render() { - const { didError } = this.state; - const { children } = this.props; - - if (didError) { - return null; - } - return children; - } -} - -export const AD_MARGIN_TOP = 3; -export const AD_MARGIN_BOTTOM = 3; -export const AD_HEIGHT = 126; -// Add more height on mobile as the text tends to wrap beyond the image height. -export const AD_HEIGHT_MOBILE = 126 + 16; - -// https://stackoverflow.com/a/20084661 -function isBot() { - return /bot|googlebot|crawler|spider|robot|crawling/i.test(navigator.userAgent); -} - -export default function Ad() { - const [adblock, setAdblock] = React.useState(null); - const [carbonOut, setCarbonOut] = React.useState(null); - - const { current: randomAdblock } = React.useRef(Math.random()); - const { current: randomInHouse } = React.useRef(Math.random()); - - let children; - let label; - // Hide the content to google bot to avoid its indexation. - if ((typeof window !== 'undefined' && isBot()) || disableAd) { - children = ; - } else if (adblock) { - if (randomAdblock < 0.2) { - children = ; - label = 'in-house-adblock'; - } else { - children = ; - label = 'in-house'; - } - } else if (carbonOut) { - children = ; - label = 'in-house-carbon'; - } else { - children = ; - label = 'carbon'; - } - - const ad = React.useContext(AdContext); - const eventLabel = label ? `${label}-${ad.placement}-${adShape}` : null; - - const timerAdblock = React.useRef(); - - const checkAdblock = React.useCallback( - (attempt = 1) => { - if ( - document.querySelector('.ea-placement') || - document.querySelector('#carbonads') || - document.querySelector('.carbonads') || - carbonOut - ) { - if ( - document.querySelector('#carbonads a') && - document.querySelector('#carbonads a')?.getAttribute('href') === - 'https://material-ui-next.com/discover-more/backers' - ) { - setCarbonOut(true); - } - - setAdblock(false); - return; - } - - if (attempt < 30) { - timerAdblock.current = setTimeout(() => { - checkAdblock(attempt + 1); - }, 500); - } - - if (attempt > 6) { - setAdblock(true); - } - }, - [carbonOut], - ); - - React.useEffect(() => { - if (disableAd) { - return undefined; - } - checkAdblock(); - - return () => { - clearTimeout(timerAdblock.current); - }; - }, [checkAdblock]); - - React.useEffect(() => { - // Avoid an exceed on the Google Analytics quotas. - if (Math.random() > ((process.env.GA_ADS_DISPLAY_RATIO ?? 0.1) as number) || !eventLabel) { - return undefined; - } - - const delay = setTimeout(() => { - window.gtag('event', 'ad', { - eventAction: 'display', - eventLabel, - }); - }, 2500); - - return () => { - clearTimeout(delay); - }; - }, [eventLabel]); - - return ( - ({ - position: 'relative', - display: 'block', - mt: AD_MARGIN_TOP, - mb: AD_MARGIN_BOTTOM, - minHeight: AD_HEIGHT_MOBILE, - [theme.breakpoints.up('sm')]: { - minHeight: AD_HEIGHT, - }, - ...(adShape === 'image' && {}), - ...(adShape === 'inline' && { - display: 'flex', - alignItems: 'flex-end', - }), - })} - data-ga-event-category="ad" - data-ga-event-action="click" - data-ga-event-label={eventLabel} - className="Ad-root" - > - {children} - - ); -} +export { Ad as default } from '@mui/docs/Ad'; diff --git a/docs/src/modules/components/AdInHouse.tsx b/docs/src/modules/components/AdInHouse.tsx deleted file mode 100644 index e158a53adaa8a7..00000000000000 --- a/docs/src/modules/components/AdInHouse.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import AdDisplay, { Ad } from 'docs/src/modules/components/AdDisplay'; - -export default function AdInHouse(props: { ad: Omit }) { - const { ad } = props; - - return ; -} - -AdInHouse.propTypes = { - ad: PropTypes.object.isRequired, -}; diff --git a/docs/src/modules/components/ApiPage.js b/docs/src/modules/components/ApiPage.js index abb59ef7b7df7f..d0515266588688 100644 --- a/docs/src/modules/components/ApiPage.js +++ b/docs/src/modules/components/ApiPage.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { exactProp } from '@mui/utils'; import Typography from '@mui/material/Typography'; import Alert from '@mui/material/Alert'; -import AdGuest from 'docs/src/modules/components/AdGuest'; +import { Ad, AdGuest } from '@mui/docs/Ad'; import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded'; import WarningRoundedIcon from '@mui/icons-material/WarningRounded'; import { useTranslate, useUserLanguage } from '@mui/docs/i18n'; @@ -13,7 +13,6 @@ import { BrandingProvider } from '@mui/docs/branding'; import { SectionTitle } from '@mui/docs/SectionTitle'; import { MarkdownElement } from '@mui/docs/MarkdownElement'; import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs'; -import Ad from 'docs/src/modules/components/Ad'; import PropertiesSection, { getPropsToC, } from 'docs/src/modules/components/ApiPage/sections/PropertiesSection'; diff --git a/docs/src/modules/components/AppLayoutDocs.js b/docs/src/modules/components/AppLayoutDocs.js index c711ea40973774..5b700a3b49443e 100644 --- a/docs/src/modules/components/AppLayoutDocs.js +++ b/docs/src/modules/components/AppLayoutDocs.js @@ -4,20 +4,20 @@ import { useRouter } from 'next/router'; import { styled } from '@mui/material/styles'; import { exactProp } from '@mui/utils'; import GlobalStyles from '@mui/material/GlobalStyles'; +import { + AdManager, + AD_MARGIN_TOP, + AD_HEIGHT, + AD_HEIGHT_MOBILE, + AD_MARGIN_BOTTOM, +} from '@mui/docs/Ad'; import Head from 'docs/src/modules/components/Head'; import AppFrame from 'docs/src/modules/components/AppFrame'; import AppContainer from 'docs/src/modules/components/AppContainer'; import AppTableOfContents from 'docs/src/modules/components/AppTableOfContents'; -import AdManager from 'docs/src/modules/components/AdManager'; import AppLayoutDocsFooter from 'docs/src/modules/components/AppLayoutDocsFooter'; import BackToTop from 'docs/src/modules/components/BackToTop'; import getProductInfoFromUrl from 'docs/src/modules/utils/getProductInfoFromUrl'; -import { - AD_MARGIN_TOP, - AD_HEIGHT, - AD_HEIGHT_MOBILE, - AD_MARGIN_BOTTOM, -} from 'docs/src/modules/components/Ad'; import { convertProductIdToName } from 'docs/src/modules/components/AppSearch'; const TOC_WIDTH = 242; diff --git a/docs/src/modules/components/Demo.js b/docs/src/modules/components/Demo.js index 96446bf66006ea..2e40c05819796f 100644 --- a/docs/src/modules/components/Demo.js +++ b/docs/src/modules/components/Demo.js @@ -20,7 +20,6 @@ import DemoSandbox from 'docs/src/modules/components/DemoSandbox'; import ReactRunner from 'docs/src/modules/components/ReactRunner'; import DemoEditor from 'docs/src/modules/components/DemoEditor'; import DemoEditorError from 'docs/src/modules/components/DemoEditorError'; -import { AdCarbonInline } from 'docs/src/modules/components/AdCarbon'; import { pathnameToLanguage } from 'docs/src/modules/utils/helpers'; import { useCodeVariant } from 'docs/src/modules/utils/codeVariant'; import { useCodeStyling } from 'docs/src/modules/utils/codeStylingSolution'; @@ -28,6 +27,7 @@ import { CODE_VARIANTS, CODE_STYLING } from 'docs/src/modules/constants'; import { useUserLanguage, useTranslate } from '@mui/docs/i18n'; import stylingSolutionMapping from 'docs/src/modules/utils/stylingSolutionMapping'; import DemoToolbarRoot from 'docs/src/modules/components/DemoToolbarRoot'; +import { AdCarbonInline } from '@mui/docs/Ad'; import { BrandingProvider, blue, blueDark, grey } from '@mui/docs/branding'; /** diff --git a/docs/src/modules/components/MarkdownDocs.js b/docs/src/modules/components/MarkdownDocs.js index 5d1dc61a55b3c8..921451a9a7af93 100644 --- a/docs/src/modules/components/MarkdownDocs.js +++ b/docs/src/modules/components/MarkdownDocs.js @@ -4,13 +4,12 @@ import { useRouter } from 'next/router'; import { useTheme } from '@mui/system'; import { exactProp } from '@mui/utils'; import { CssVarsProvider as JoyCssVarsProvider, useColorScheme } from '@mui/joy/styles'; +import { Ad, AdGuest } from '@mui/docs/Ad'; import RichMarkdownElement from 'docs/src/modules/components/RichMarkdownElement'; import { pathnameToLanguage } from 'docs/src/modules/utils/helpers'; import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs'; import { useUserLanguage } from '@mui/docs/i18n'; import { BrandingProvider } from '@mui/docs/branding'; -import Ad from 'docs/src/modules/components/Ad'; -import AdGuest from 'docs/src/modules/components/AdGuest'; function JoyModeObserver({ mode }) { const { setMode } = useColorScheme(); diff --git a/docs/src/modules/components/MarkdownDocsV2.js b/docs/src/modules/components/MarkdownDocsV2.js index cef3c473ccc059..ff7ac4047ce39a 100644 --- a/docs/src/modules/components/MarkdownDocsV2.js +++ b/docs/src/modules/components/MarkdownDocsV2.js @@ -5,6 +5,7 @@ import kebabCase from 'lodash/kebabCase'; import { useTheme } from '@mui/system'; import { exactProp } from '@mui/utils'; import { CssVarsProvider as JoyCssVarsProvider, useColorScheme } from '@mui/joy/styles'; +import { Ad, AdGuest } from '@mui/docs/Ad'; import ComponentsApiContent from 'docs/src/modules/components/ComponentsApiContent'; import HooksApiContent from 'docs/src/modules/components/HooksApiContent'; import { getTranslatedHeader as getComponentTranslatedHeader } from 'docs/src/modules/components/ApiPage'; @@ -13,10 +14,8 @@ import { pathnameToLanguage } from 'docs/src/modules/utils/helpers'; import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs'; import { useTranslate, useUserLanguage } from '@mui/docs/i18n'; import { BrandingProvider } from '@mui/docs/branding'; -import Ad from 'docs/src/modules/components/Ad'; import { HEIGHT as AppFrameHeight } from 'docs/src/modules/components/AppFrame'; import { HEIGHT as TabsHeight } from 'docs/src/modules/components/ComponentPageTabs'; -import AdGuest from 'docs/src/modules/components/AdGuest'; import { getPropsToC } from 'docs/src/modules/components/ApiPage/sections/PropertiesSection'; import { getClassesToC } from 'docs/src/modules/components/ApiPage/sections/ClassesSection'; diff --git a/docs/src/modules/utils/loadScript.js b/docs/src/modules/utils/loadScript.js deleted file mode 100644 index 9b7ef0a0cc3c49..00000000000000 --- a/docs/src/modules/utils/loadScript.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function loadScript(src, position) { - const script = document.createElement('script'); - script.setAttribute('async', ''); - script.src = src; - position.appendChild(script); - - return script; -} diff --git a/docs/types/ga.d.ts b/docs/types/ga.d.ts deleted file mode 100644 index 77c372691d0f4d..00000000000000 --- a/docs/types/ga.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import Gtag from '@types/gtag.js'; - -declare global { - interface Window { - gtag: Gtag.Gtag; - } -} diff --git a/packages/mui-docs/package.json b/packages/mui-docs/package.json index 868c81dd53fafe..c18d44ee09551d 100644 --- a/packages/mui-docs/package.json +++ b/packages/mui-docs/package.json @@ -44,6 +44,7 @@ "devDependencies": { "@mui/icons-material": "workspace:*", "@mui/material": "workspace:*", + "@types/gtag.js": "^0.0.20", "@types/node": "^18.19.41", "@types/prop-types": "^15.7.12", "@types/react": "^18.3.3", @@ -58,7 +59,8 @@ "@types/react": "^17.0.0 || ^18.0.0", "chai": "^4.4.1", "next": "^13.5.1 || ^14", - "react": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0", + "csstype": "^3.1.3" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/mui-docs/src/Ad/Ad.tsx b/packages/mui-docs/src/Ad/Ad.tsx new file mode 100644 index 00000000000000..b056ca5ec54a7d --- /dev/null +++ b/packages/mui-docs/src/Ad/Ad.tsx @@ -0,0 +1,240 @@ +import * as React from 'react'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import Paper from '@mui/material/Paper'; +import { useTranslate } from '../i18n'; +import AdCarbon from './AdCarbon'; +import AdInHouse from './AdInHouse'; +import { AdContext, adShape } from './AdManager'; +import { useAdConfig } from './AdProvider'; + +function PleaseDisableAdblock() { + const t = useTranslate(); + + return ( + + + {t('likeMui')} + + + {t('adblock')} + + + {t('thanks')}{' '} + + ❤️ + + + + ); +} + +const disableAd = + process.env.NODE_ENV !== 'production' && process.env.ENABLE_AD_IN_DEV_MODE !== 'true'; +const inHouseAds = [ + { + name: 'scaffoldhub', + link: 'https://v2.scaffoldhub.io/scaffolds/react-material-ui?partner=1', + img: '/static/ads-in-house/scaffoldhub.png', + description: 'ScaffoldHub. Automate building your full-stack Material UI web-app.', + }, + { + name: 'templates', + link: 'https://mui.com/store/?utm_source=docs&utm_medium=referral&utm_campaign=in-house-templates', + img: '/static/ads-in-house/themes-2.jpg', + description: + 'Premium Templates. Start your project with the best templates for admins, dashboards, and more.', + }, + { + name: 'themes', + link: 'https://mui.com/store/?utm_source=docs&utm_medium=referral&utm_campaign=in-house-themes', + img: '/static/ads-in-house/themes.png', + description: + 'Premium Themes. Kickstart your application development with a ready-made theme.', + }, + { + name: 'tidelift', + link: 'https://tidelift.com/subscription/pkg/npm-material-ui?utm_source=npm-material-ui&utm_medium=referral&utm_campaign=enterprise&utm_content=ad', + img: '/static/ads-in-house/tidelift.png', + description: + 'MUI for enterprise. Save time and reduce risk. Managed open source — backed by maintainers.', + }, + { + name: 'figma', + link: 'https://mui.com/store/items/figma-react/?utm_source=docs&utm_medium=referral&utm_campaign=in-house-figma', + img: '/static/ads-in-house/figma.png', + description: + 'For Figma. A large UI kit with over 600 handcrafted Material UI, MUI X, Joy UI components 🎨.', + }, +]; + +class AdErrorBoundary extends React.Component<{ + eventLabel: string | null; + children?: React.ReactNode | undefined; +}> { + state = { didError: false }; + + static getDerivedStateFromError() { + return { didError: true }; + } + + componentDidCatch() { + // send explicit `'null'` + const eventLabel = String(this.props.eventLabel); + // TODO: Use proper error monitoring service (for example Sentry) instead + + window.gtag('event', 'ad', { + eventAction: 'crash', + eventLabel, + }); + } + + render() { + const { didError } = this.state; + const { children } = this.props; + + if (didError) { + return null; + } + return children; + } +} + +export const AD_MARGIN_TOP = 3; +export const AD_MARGIN_BOTTOM = 3; +export const AD_HEIGHT = 126; +// Add more height on mobile as the text tends to wrap beyond the image height. +export const AD_HEIGHT_MOBILE = 126 + 16; + +// https://stackoverflow.com/a/20084661 +function isBot() { + return /bot|googlebot|crawler|spider|robot|crawling/i.test(navigator.userAgent); +} + +export function Ad() { + const [adblock, setAdblock] = React.useState(null); + const [carbonOut, setCarbonOut] = React.useState(null); + + const { current: randomAdblock } = React.useRef(Math.random()); + const { current: randomInHouse } = React.useRef(Math.random()); + + let children; + let label; + // Hide the content to google bot to avoid its indexation. + if ((typeof window !== 'undefined' && isBot()) || disableAd) { + children = ; + } else if (adblock) { + if (randomAdblock < 0.2) { + children = ; + label = 'in-house-adblock'; + } else { + children = ; + label = 'in-house'; + } + } else if (carbonOut) { + children = ; + label = 'in-house-carbon'; + } else { + children = ; + label = 'carbon'; + } + + const ad = React.useContext(AdContext); + const eventLabel = label ? `${label}-${ad.placement}-${adShape}` : null; + + const timerAdblock = React.useRef>(); + + const checkAdblock = React.useCallback( + (attempt = 1) => { + if ( + document.querySelector('.ea-placement') || + document.querySelector('#carbonads') || + document.querySelector('.carbonads') || + carbonOut + ) { + if ( + document.querySelector('#carbonads a') && + document.querySelector('#carbonads a')?.getAttribute('href') === + 'https://material-ui-next.com/discover-more/backers' + ) { + setCarbonOut(true); + } + + setAdblock(false); + return; + } + + if (attempt < 30) { + timerAdblock.current = setTimeout(() => { + checkAdblock(attempt + 1); + }, 500); + } + + if (attempt > 6) { + setAdblock(true); + } + }, + [carbonOut], + ); + + React.useEffect(() => { + if (disableAd) { + return undefined; + } + checkAdblock(); + + return () => { + clearTimeout(timerAdblock.current); + }; + }, [checkAdblock]); + + const { GADisplayRatio } = useAdConfig(); + React.useEffect(() => { + // Avoid an exceed on the Google Analytics quotas. + if (Math.random() > (GADisplayRatio ?? 0.1) || !eventLabel) { + return undefined; + } + + const delay = setTimeout(() => { + window.gtag('event', 'ad', { + eventAction: 'display', + eventLabel, + }); + }, 2500); + + return () => { + clearTimeout(delay); + }; + }, [GADisplayRatio, eventLabel]); + + return ( + ({ + position: 'relative', + display: 'block', + mt: AD_MARGIN_TOP, + mb: AD_MARGIN_BOTTOM, + minHeight: AD_HEIGHT_MOBILE, + [theme.breakpoints.up('sm')]: { + minHeight: AD_HEIGHT, + }, + ...(adShape === 'image' && {}), + ...(adShape === 'inline' && { + display: 'flex', + alignItems: 'flex-end', + }), + })} + data-ga-event-category="ad" + data-ga-event-action="click" + data-ga-event-label={eventLabel} + className="Ad-root" + > + {children} + + ); +} diff --git a/docs/src/modules/components/AdCarbon.tsx b/packages/mui-docs/src/Ad/AdCarbon.tsx similarity index 92% rename from docs/src/modules/components/AdCarbon.tsx rename to packages/mui-docs/src/Ad/AdCarbon.tsx index cf39fdc5b7c431..73727ae49dd714 100644 --- a/docs/src/modules/components/AdCarbon.tsx +++ b/packages/mui-docs/src/Ad/AdCarbon.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { styled } from '@mui/material/styles'; -import loadScript from 'docs/src/modules/utils/loadScript'; -import AdDisplay from 'docs/src/modules/components/AdDisplay'; -import { adStylesObject } from 'docs/src/modules/components/ad.styles'; +import loadScript from '../utils/loadScript'; +import AdDisplay from './AdDisplay'; +import { adBodyImageStyles } from './ad.styles'; type CarbonAd = { pixel: string; @@ -14,7 +14,7 @@ type CarbonAd = { description: string; }; const CarbonRoot = styled('span')(({ theme }) => { - const styles = adStylesObject['body-image'](theme); + const styles = adBodyImageStyles(theme); return { width: '100%', @@ -38,7 +38,7 @@ const CarbonRoot = styled('span')(({ theme }) => { }); function AdCarbonImage() { - const ref = React.useRef(null); + const ref = React.useRef(null); React.useEffect(() => { // The isolation logic of carbonads is broken. diff --git a/docs/src/modules/components/AdDisplay.tsx b/packages/mui-docs/src/Ad/AdDisplay.tsx similarity index 78% rename from docs/src/modules/components/AdDisplay.tsx rename to packages/mui-docs/src/Ad/AdDisplay.tsx index 21eddc2246e4ac..6fa3d0ad7f6a08 100644 --- a/docs/src/modules/components/AdDisplay.tsx +++ b/packages/mui-docs/src/Ad/AdDisplay.tsx @@ -1,13 +1,12 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; import { styled } from '@mui/material/styles'; -import { adShape } from 'docs/src/modules/components/AdManager'; -import { adStylesObject } from 'docs/src/modules/components/ad.styles'; -import { useTranslate } from '@mui/docs/i18n'; +import { useTranslate } from '../i18n'; +import { adShape } from './AdManager'; +import { adBodyImageStyles, adBodyInlineStyles } from './ad.styles'; +import { useAdConfig } from './AdProvider'; const InlineShape = styled('span')(({ theme }) => { - const styles = adStylesObject['body-inline'](theme); - + const styles = adBodyInlineStyles(theme); return { ...styles.root, '& img': styles.img, @@ -19,8 +18,7 @@ const InlineShape = styled('span')(({ theme }) => { }); const ImageShape = styled('span')(({ theme }) => { - const styles = adStylesObject['body-image'](theme); - + const styles = adBodyImageStyles(theme); return { ...styles.root, '& img': styles.img, @@ -31,7 +29,7 @@ const ImageShape = styled('span')(({ theme }) => { }; }); -export interface Ad { +export interface AdParameters { name: string; link: string; img?: string; @@ -40,7 +38,7 @@ export interface Ad { label: string; } interface AdDisplayProps { - ad: Ad; + ad: AdParameters; className?: string; shape?: 'auto' | 'inline' | 'image'; } @@ -49,9 +47,11 @@ export default function AdDisplay(props: AdDisplayProps) { const { ad, className, shape: shapeProp = 'auto' } = props; const t = useTranslate(); + const { GADisplayRatio } = useAdConfig(); + React.useEffect(() => { // Avoid an exceed on the Google Analytics quotas. - if (Math.random() > ((process.env.GA_ADS_DISPLAY_RATIO ?? 0.1) as number) || !ad.label) { + if (Math.random() > (GADisplayRatio ?? 0.1) || !ad.label) { return; } @@ -59,7 +59,7 @@ export default function AdDisplay(props: AdDisplayProps) { eventAction: 'display', eventLabel: ad.label, }); - }, [ad.label]); + }, [GADisplayRatio, ad.label]); const shape = shapeProp === 'auto' ? adShape : shapeProp; @@ -95,9 +95,3 @@ export default function AdDisplay(props: AdDisplayProps) { ); /* eslint-enable react/no-danger */ } - -AdDisplay.propTypes = { - ad: PropTypes.object.isRequired, - className: PropTypes.string, - shape: PropTypes.oneOf(['inline', 'auto']), -}; diff --git a/docs/src/modules/components/AdGuest.tsx b/packages/mui-docs/src/Ad/AdGuest.tsx similarity index 73% rename from docs/src/modules/components/AdGuest.tsx rename to packages/mui-docs/src/Ad/AdGuest.tsx index e97f5440952d55..c11c00809879bd 100644 --- a/docs/src/modules/components/AdGuest.tsx +++ b/packages/mui-docs/src/Ad/AdGuest.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; import Portal from '@mui/material/Portal'; -import { AdContext } from 'docs/src/modules/components/AdManager'; +import { AdContext } from './AdManager'; -interface AdGuestProps { +export interface AdGuestProps { /** * The querySelector use to target the element which will include the ad. */ @@ -11,7 +10,7 @@ interface AdGuestProps { children?: React.ReactNode | undefined; } -export default function AdGuest(props: AdGuestProps) { +function AdGuest(props: AdGuestProps) { const { classSelector = '.description', children } = props; const ad = React.useContext(AdContext); @@ -40,7 +39,4 @@ export default function AdGuest(props: AdGuestProps) { ); } -AdGuest.propTypes = { - children: PropTypes.node, - classSelector: PropTypes.string, -}; +export { AdGuest }; diff --git a/packages/mui-docs/src/Ad/AdInHouse.tsx b/packages/mui-docs/src/Ad/AdInHouse.tsx new file mode 100644 index 00000000000000..67fd3b1a2af3aa --- /dev/null +++ b/packages/mui-docs/src/Ad/AdInHouse.tsx @@ -0,0 +1,8 @@ +import * as React from 'react'; +import AdDisplay, { AdParameters } from './AdDisplay'; + +export default function AdInHouse(props: { ad: Omit }) { + const { ad } = props; + + return ; +} diff --git a/docs/src/modules/components/AdManager.tsx b/packages/mui-docs/src/Ad/AdManager.tsx similarity index 82% rename from docs/src/modules/components/AdManager.tsx rename to packages/mui-docs/src/Ad/AdManager.tsx index 3fccd1c7ff5594..7168333c9d3cdf 100644 --- a/docs/src/modules/components/AdManager.tsx +++ b/packages/mui-docs/src/Ad/AdManager.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/material/utils'; type AdPortal = { @@ -26,7 +25,7 @@ const randomSession = Math.random(); // 80% body-image export const adShape = randomSession < 0.2 ? 'inline' : 'image'; -export default function AdManager({ classSelector = '.description', children }: AdManagerProps) { +export function AdManager({ classSelector = '.description', children }: AdManagerProps) { const [portal, setPortal] = React.useState({ placement: 'body-top', element: null }); useEnhancedEffect(() => { @@ -36,8 +35,3 @@ export default function AdManager({ classSelector = '.description', children }: return {children}; } - -AdManager.propTypes = { - children: PropTypes.node, - classSelector: PropTypes.string, -}; diff --git a/packages/mui-docs/src/Ad/AdProvider.tsx b/packages/mui-docs/src/Ad/AdProvider.tsx new file mode 100644 index 00000000000000..2a1eac40adaca7 --- /dev/null +++ b/packages/mui-docs/src/Ad/AdProvider.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; + +export interface AdConfig { + /** + * The ratio of "ad display" event sent to Google Analytics. + * Used to avoid an exceed on the Google Analytics quotas. + * @default 0.1 + */ + GADisplayRatio: number; +} + +export interface AdProviderProps { + children: React.ReactNode; + config?: Partial; +} + +const AdConfigContext = React.createContext(null); + +export function AdProvider(props: AdProviderProps) { + const { children, config } = props; + + const value = React.useMemo(() => ({ GADisplayRatio: 0.1, ...config }), [config]); + + return {children}; +} + +export function useAdConfig() { + const config = React.useContext(AdConfigContext); + if (!config) { + throw new Error( + 'Could not find docs ad config context value; please ensure the component is wrapped in a ', + ); + } + return config; +} diff --git a/docs/src/modules/components/ad.styles.ts b/packages/mui-docs/src/Ad/ad.styles.ts similarity index 86% rename from docs/src/modules/components/ad.styles.ts rename to packages/mui-docs/src/Ad/ad.styles.ts index dffa082eb2f157..9c9f662c66af8a 100644 --- a/docs/src/modules/components/ad.styles.ts +++ b/packages/mui-docs/src/Ad/ad.styles.ts @@ -1,7 +1,6 @@ import { alpha, Theme } from '@mui/material/styles'; -import { adShape } from 'docs/src/modules/components/AdManager'; -const adBodyImageStyles = (theme: Theme) => ({ +export const adBodyImageStyles = (theme: Theme) => ({ root: { display: 'block', overflow: 'hidden', @@ -44,7 +43,7 @@ const adBodyImageStyles = (theme: Theme) => ({ }, }); -const adBodyInlineStyles = (theme: Theme) => { +export const adBodyInlineStyles = (theme: Theme) => { const baseline = adBodyImageStyles(theme); return { @@ -91,10 +90,3 @@ const adBodyInlineStyles = (theme: Theme) => { }, }; }; - -export const adStylesObject = { - 'body-image': adBodyImageStyles, - 'body-inline': adBodyInlineStyles, -}; - -export default adStylesObject[`body-${adShape}`]; diff --git a/packages/mui-docs/src/Ad/index.ts b/packages/mui-docs/src/Ad/index.ts new file mode 100644 index 00000000000000..59c9abf8e91fff --- /dev/null +++ b/packages/mui-docs/src/Ad/index.ts @@ -0,0 +1,7 @@ +/// + +export * from './Ad'; +export * from './AdManager'; +export * from './AdProvider'; +export * from './AdGuest'; +export { AdCarbonInline } from './AdCarbon'; diff --git a/packages/mui-docs/src/DocsProvider/DocsProvider.tsx b/packages/mui-docs/src/DocsProvider/DocsProvider.tsx index debe6afeb5a70e..4cefa6b1e3d7fc 100644 --- a/packages/mui-docs/src/DocsProvider/DocsProvider.tsx +++ b/packages/mui-docs/src/DocsProvider/DocsProvider.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { Translations, UserLanguageProvider } from '../i18n'; +import { AdConfig, AdProvider } from '../Ad'; export interface DocsConfig { LANGUAGES: string[]; @@ -12,6 +13,7 @@ const DocsConfigContext = React.createContext(null); export interface DocsProviderProps { config: DocsConfig; + adConfig?: Partial; defaultUserLanguage: string; children?: React.ReactNode; translations?: Translations; @@ -19,15 +21,18 @@ export interface DocsProviderProps { export function DocsProvider({ config, + adConfig, defaultUserLanguage, translations, children, }: DocsProviderProps) { return ( - - {children} - + + + {children} + + ); } diff --git a/packages/mui-docs/src/utils/loadScript.ts b/packages/mui-docs/src/utils/loadScript.ts new file mode 100644 index 00000000000000..5c245efc2d03d9 --- /dev/null +++ b/packages/mui-docs/src/utils/loadScript.ts @@ -0,0 +1,10 @@ +export default function loadScript(src: string, position: HTMLElement | null) { + const script = document.createElement('script'); + script.setAttribute('async', ''); + script.src = src; + if (position) { + position.appendChild(script); + } + + return script; +} diff --git a/packages/mui-docs/tsconfig.json b/packages/mui-docs/tsconfig.json index 63ed1fc8920b4f..60a823f5265032 100644 --- a/packages/mui-docs/tsconfig.json +++ b/packages/mui-docs/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "skipLibCheck": true, "resolveJsonModule": true, - "types": ["react", "node"] + "types": ["react", "node", "csstype"] }, "include": ["src/**/*", "test/**/*"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f963b97e552e4e..f4d46d74fffdcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -834,9 +834,6 @@ importers: '@types/css-mediaquery': specifier: ^0.1.4 version: 0.1.4 - '@types/gtag.js': - specifier: ^0.0.20 - version: 0.0.20 '@types/json2mq': specifier: ^0.2.2 version: 0.2.2 @@ -1411,6 +1408,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + csstype: + specifier: ^3.1.3 + version: 3.1.3 nprogress: specifier: ^0.2.0 version: 0.2.0 @@ -1424,6 +1424,9 @@ importers: '@mui/material': specifier: workspace:* version: link:../mui-material/build + '@types/gtag.js': + specifier: ^0.0.20 + version: 0.0.20 '@types/node': specifier: ^18.19.41 version: 18.19.42