init project
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled

This commit is contained in:
how2ice
2025-12-12 14:26:25 +09:00
commit 005cf56baf
43188 changed files with 1079531 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
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 (
<Paper
component="span"
elevation={0}
sx={{ display: 'block', p: 1.5, border: '2px solid', borderColor: 'primary.main' }}
>
<Typography variant="body2" component="span" gutterBottom sx={{ display: 'block' }}>
{t('likeMui')}
</Typography>
<Typography variant="body2" component="span" gutterBottom sx={{ display: 'block' }}>
{t('adblock')}
</Typography>
<Typography variant="body2" component="span" sx={{ display: 'block' }}>
{t('thanks')}{' '}
<span role="img" aria-label={t('emojiLove')}>
</span>
</Typography>
</Paper>
);
}
const disableAd =
process.env.NODE_ENV !== 'production' && process.env.ENABLE_AD_IN_DEV_MODE !== 'true';
const inHouseAds = [
{
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',
descriptionHeader: 'Premium Templates',
description: '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',
descriptionHeader: 'Premium Themes',
description: 'Kickstart your application development with a ready-made theme.',
},
{
name: 'tidelift',
link: 'https://tidelift.com/',
img: '/static/ads-in-house/tidelift.png',
descriptionHeader: 'MUI for enterprise',
description: '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',
descriptionHeader: 'For Figma',
description:
'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 | boolean>(null);
const [carbonOut, setCarbonOut] = React.useState<null | boolean>(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 = <span />;
} else if (adblock) {
if (randomAdblock < 0.2) {
children = <PleaseDisableAdblock />;
label = 'in-house-adblock';
} else {
children = <AdInHouse ad={inHouseAds[Math.floor(inHouseAds.length * randomInHouse)]} />;
label = 'in-house';
}
} else if (carbonOut) {
children = <AdInHouse ad={inHouseAds[Math.floor(inHouseAds.length * randomInHouse)]} />;
label = 'in-house-carbon';
} else {
children = <AdCarbon />;
label = 'carbon';
}
const ad = React.useContext(AdContext);
const eventLabel = label ? `${label}-${ad.placement}-${adShape}` : null;
const timerAdblock = React.useRef<ReturnType<typeof setTimeout>>(undefined);
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 (
<Box
component="span"
sx={(theme) => ({
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}
>
<AdErrorBoundary eventLabel={eventLabel}>{children}</AdErrorBoundary>
</Box>
);
}

View File

@@ -0,0 +1,153 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import loadScript from '../utils/loadScript';
import AdDisplay from './AdDisplay';
import { adBodyImageStyles } from './ad.styles';
type CarbonAd = {
pixel: string;
timestamp: string;
statimp: string;
statlink: string;
image: string;
company: string;
description: string;
};
const CarbonRoot = styled('span')(({ theme }) => {
const styles = adBodyImageStyles(theme);
return {
width: '100%',
'& > div': {
// The isolation logic of carbonads is broken.
// Once the script starts loading, it will asynchronous resolve, with no way to stop it.
// This leads to duplication of the ad.
//
// To solve the issue, we only display the #carbonads div
display: 'none',
},
'& #carbonads': {
...styles.root,
'& .carbon-img': styles.imgWrapper,
'& img': styles.img,
'& a, & a:hover': styles.a,
'& .carbon-text': styles.description,
'& .carbon-poweredby': styles.poweredby,
},
};
});
function AdCarbonImage() {
const ref = React.useRef<HTMLElement>(null);
React.useEffect(() => {
// The isolation logic of carbonads is broken.
// Once the script starts loading, it will asynchronous resolve, with no way to stop it.
// This leads to duplication of the ad.
//
// To solve the issue, for example StrictModel double effect execution, we debounce the load action.
const load = setTimeout(() => {
// The DOM node could have unmounted at this point.
if (!ref.current) {
return;
}
const script = loadScript(
'https://cdn.carbonads.com/carbon.js?serve=CKYIL27L&placement=material-uicom',
ref.current,
);
script.id = '_carbonads_js';
});
return () => {
clearTimeout(load);
};
}, []);
return <CarbonRoot ref={ref} />;
}
export function AdCarbonInline() {
const [ad, setAd] = React.useState<CarbonAd | null>(null);
React.useEffect(() => {
let active = true;
let attempt = 0;
(async () => {
async function tryFetch() {
if (attempt >= 10 || !active) {
return null;
}
attempt += 1;
let response;
try {
response = await fetch('https://srv.buysellads.com/ads/CE7DC23W.json');
} catch (err) {
// Ad blocker crashes this request
return null;
}
const data = await response.json();
// Inspired by https://github.com/Semantic-Org/Semantic-UI-React/blob/2c7134128925dd831de85011e3eb0ec382ba7f73/docs/src/components/CarbonAd/CarbonAdNative.js#L9
const sanitizedAd = data.ads
.filter((item: any) => Object.keys(item).length > 0)
.filter((item: any) => item.statlink)
.filter(Boolean)[0];
if (!sanitizedAd) {
return tryFetch();
}
return sanitizedAd;
}
const sanitizedAd = await tryFetch();
if (active) {
setAd(sanitizedAd);
}
})();
return () => {
active = false;
};
}, []);
return ad ? (
<React.Fragment>
{/* Impression */}
<img src={ad.statimp} alt="" style={{ display: 'none' }} />
{/* Pixel */}
{ad.pixel &&
ad.pixel
.split('||')
.map((pixel, i) => (
<img
key={i}
src={`${pixel.replace('[timestamp]', ad.timestamp)}`}
style={{ display: 'none' }}
alt=""
/>
))}
<AdDisplay
className="carbonads"
shape="inline"
ad={{
link: ad.statlink,
img: ad.image,
name: ad.company,
descriptionHeader: ad.company,
description: ad.description,
poweredby: 'Carbon',
label: 'carbon-demo-inline',
}}
/>
</React.Fragment>
) : (
<div style={{ minHeight: 52 }} />
);
}
export default function AdCarbon() {
return <AdCarbonImage />;
}

View File

@@ -0,0 +1,95 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
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 = adBodyInlineStyles(theme);
return {
...styles.root,
'& img': styles.img,
'& a, & a:hover': styles.a,
'& .AdDisplay-imageWrapper': styles.imgWrapper,
'& .AdDisplay-description': styles.description,
'& .AdDisplay-poweredby': styles.poweredby,
};
});
const ImageShape = styled('span')(({ theme }) => {
const styles = adBodyImageStyles(theme);
return {
...styles.root,
'& img': styles.img,
'& a, & a:hover': styles.a,
'& .AdDisplay-imageWrapper': styles.imgWrapper,
'& .AdDisplay-description': styles.description,
'& .AdDisplay-poweredby': styles.poweredby,
};
});
export interface AdParameters {
name: string;
link: string;
img?: string;
descriptionHeader: string;
description: string;
poweredby: string;
label: string;
}
interface AdDisplayProps {
ad: AdParameters;
className?: string;
shape?: 'auto' | 'inline' | 'image';
}
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() > (GADisplayRatio ?? 0.1) || !ad.label) {
return;
}
window.gtag('event', 'ad', {
eventAction: 'display',
eventLabel: ad.label,
});
}, [GADisplayRatio, ad.label]);
const shape = shapeProp === 'auto' ? adShape : shapeProp;
const Root = shape === 'image' ? ImageShape : InlineShape;
return (
<Root className={className}>
<a
href={ad.link}
target="_blank"
rel="noopener sponsored"
{...(ad.label
? {
'data-ga-event-category': 'ad',
'data-ga-event-action': 'click',
'data-ga-event-label': ad.label,
}
: {})}
>
<span className="AdDisplay-imageWrapper">
<img height="100" width="130" src={ad.img} alt={ad.name} />
</span>
<span className="AdDisplay-description">
<strong>{ad.descriptionHeader}</strong> - {ad.description}
</span>
</a>
<span className="AdDisplay-poweredby">
{t('adPublisher').replace('{{publisher}}', ad.poweredby)}
</span>
</Root>
);
}

View File

@@ -0,0 +1,42 @@
import * as React from 'react';
import Portal from '@mui/material/Portal';
import { AdContext } from './AdManager';
export interface AdGuestProps {
/**
* The querySelector use to target the element which will include the ad.
*/
classSelector?: string;
children?: React.ReactNode | undefined;
}
function AdGuest(props: AdGuestProps) {
const { classSelector = '.description', children } = props;
const ad = React.useContext(AdContext);
if (!ad.element) {
return null;
}
return (
<Portal
container={() => {
const element = document.querySelector(classSelector);
if (element) {
if (ad.element === element) {
element.classList.add('ad');
} else {
element.classList.remove('ad');
}
}
return ad.element;
}}
>
{children}
</Portal>
);
}
export { AdGuest };

View File

@@ -0,0 +1,7 @@
import AdDisplay, { AdParameters } from './AdDisplay';
export default function AdInHouse(props: { ad: Omit<AdParameters, 'poweredby' | 'label'> }) {
const { ad } = props;
return <AdDisplay ad={{ poweredby: 'MUI', label: `in-house-${ad.name}`, ...ad }} />;
}

View File

@@ -0,0 +1,37 @@
import * as React from 'react';
import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/material/utils';
type AdPortal = {
placement: 'body-top';
element: Element | null;
};
interface AdManagerProps {
/**
* The querySelector use to target the element which will include the ad.
*/
classSelector?: string;
children?: React.ReactNode | undefined;
}
export const AdContext = React.createContext<AdPortal>({ placement: 'body-top', element: null });
// Persisted for the whole session.
// The state is used to use different ad placements.
const randomSession = Math.random();
// Distribution profile:
// 20% body-inline
// 80% body-image
export const adShape = randomSession < 0.2 ? 'inline' : 'image';
export function AdManager({ classSelector = '.description', children }: AdManagerProps) {
const [portal, setPortal] = React.useState<AdPortal>({ placement: 'body-top', element: null });
useEnhancedEffect(() => {
const container = document.querySelector(classSelector);
setPortal({ placement: 'body-top', element: container });
}, [classSelector]);
return <AdContext.Provider value={portal}>{children}</AdContext.Provider>;
}

View File

@@ -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<AdConfig>;
}
const AdConfigContext = React.createContext<AdConfig | null>(null);
export function AdProvider(props: AdProviderProps) {
const { children, config } = props;
const value = React.useMemo(() => ({ GADisplayRatio: 0.1, ...config }), [config]);
return <AdConfigContext.Provider value={value}>{children}</AdConfigContext.Provider>;
}
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 <AdProvider>',
);
}
return config;
}

View File

@@ -0,0 +1,94 @@
import { alpha, Theme } from '@mui/material/styles';
export const adBodyImageStyles = (theme: Theme) => ({
root: {
display: 'block',
overflow: 'hidden',
border: '1px dashed',
borderColor: (theme.vars || theme).palette.divider,
borderRadius: (theme.vars || theme).shape.borderRadius,
padding: 8,
paddingLeft: 8 + 130,
[theme.breakpoints.up('sm')]: {
padding: 12,
paddingLeft: 12 + 130,
},
},
imgWrapper: {
float: 'left',
marginLeft: -130,
width: 130,
height: 100,
},
img: {
verticalAlign: 'middle',
},
a: {
color: (theme.vars || theme).palette.text.primary,
textDecoration: 'none',
},
description: {
...theme.typography.body2,
[theme.breakpoints.up('sm')]: {
...theme.typography.body1,
},
display: 'block',
marginLeft: theme.spacing(1.5),
},
poweredby: {
...theme.typography.caption,
marginLeft: theme.spacing(1.5),
color: (theme.vars || theme).palette.text.secondary,
display: 'block',
marginTop: theme.spacing(0.5),
fontWeight: theme.typography.fontWeightRegular,
},
});
export const adBodyInlineStyles = (theme: Theme) => {
const baseline = adBodyImageStyles(theme);
return {
...baseline,
root: {
display: 'block',
paddingTop: 8,
},
imgWrapper: {
display: 'none',
},
description: {
...baseline.description,
marginLeft: 0,
'&::before': {
border: '1px solid #3e8e41',
color: '#3e8e41',
marginRight: 6,
padding: '1px 5px',
borderRadius: 3,
content: '"Ad"',
fontSize: theme.typography.pxToRem(14),
},
'&::after': {
// Link
marginLeft: 4,
content: '"Get started"',
// Style taken from the Link component & MarkdownElement.
color: (theme.vars || theme).palette.primary[600],
textDecoration: 'underline',
textDecorationColor: alpha(theme.palette.primary.main, 0.4),
...theme.applyStyles('dark', {
color: (theme.vars || theme).palette.primary[300],
}),
},
},
poweredby: {
...baseline.poweredby,
marginTop: 2,
marginLeft: 0,
},
link: {
display: 'none',
},
};
};

View File

@@ -0,0 +1,7 @@
/// <reference types="gtag.js" />
export * from './Ad';
export * from './AdManager';
export * from './AdProvider';
export * from './AdGuest';
export { AdCarbonInline } from './AdCarbon';

View File

@@ -0,0 +1,199 @@
import * as React from 'react';
import { useRouter } from 'next/router';
import clipboardCopy from 'clipboard-copy';
const CodeBlockContext = React.createContext<React.MutableRefObject<HTMLDivElement | null>>({
current: null,
});
/**
* How to use: spread the handlers to the .MuiCode-root
*
* The html structure should be:
* <div className="MuiCode-root">
* <pre>...</pre>
* <button className="MuiCode-copy">...</button>
* </div>
*/
export function useCodeCopy(): React.HTMLAttributes<HTMLDivElement> {
const rootNode = React.useContext(CodeBlockContext);
return {
onMouseEnter: (event) => {
rootNode.current = event.currentTarget;
},
onMouseLeave: (event) => {
if (rootNode.current === event.currentTarget) {
(rootNode.current.querySelector('.MuiCode-copy') as null | HTMLButtonElement)?.blur();
rootNode.current = null;
}
},
onFocus: (event) => {
rootNode.current = event.currentTarget;
},
onBlur: (event) => {
if (rootNode.current === event.currentTarget) {
rootNode.current = null;
}
},
};
}
function InitCodeCopy() {
const rootNode = React.useContext(CodeBlockContext);
const router = useRouter();
React.useEffect(() => {
let key = 'Ctrl + ';
if (typeof window !== 'undefined') {
const macOS = window.navigator.platform.toUpperCase().includes('MAC');
if (macOS) {
key = '⌘';
}
}
const codeRoots = document.getElementsByClassName(
'MuiCode-root',
) as HTMLCollectionOf<HTMLDivElement>;
if (codeRoots !== null) {
const listeners: Array<() => void> = [];
Array.from(codeRoots).forEach((elm) => {
const handleMouseEnter = () => {
rootNode.current = elm;
};
elm.addEventListener('mouseenter', handleMouseEnter);
listeners.push(() => elm.removeEventListener('mouseenter', handleMouseEnter));
const handleMouseLeave = () => {
if (rootNode.current === elm) {
(rootNode.current.querySelector('.MuiCode-copy') as null | HTMLButtonElement)?.blur();
rootNode.current = null;
}
};
elm.addEventListener('mouseleave', handleMouseLeave);
listeners.push(() => elm.removeEventListener('mouseleave', handleMouseLeave));
const handleFocusin = () => {
// use `focusin` because it bubbles from the copy button
rootNode.current = elm;
};
elm.addEventListener('focusin', handleFocusin);
listeners.push(() => elm.removeEventListener('focusin', handleFocusin));
const handleFocusout = () => {
// use `focusout` because it bubbles from the copy button
if (rootNode.current === elm) {
rootNode.current = null;
}
};
elm.addEventListener('focusout', handleFocusout);
listeners.push(() => elm.removeEventListener('focusout', handleFocusout));
async function handleClick(event: MouseEvent) {
const trigger = event.currentTarget as HTMLButtonElement;
const pre = (event.currentTarget as Element)?.previousElementSibling as Element;
const textNode = trigger.childNodes[0];
textNode.nodeValue = textNode.textContent?.replace('Copy', 'Copied') || null;
trigger.dataset.copied = 'true';
setTimeout(() => {
if (trigger) {
textNode.nodeValue = textNode.textContent?.replace('Copied', 'Copy') || null;
delete trigger.dataset.copied;
}
}, 2000);
try {
if (pre.textContent) {
await clipboardCopy(pre.textContent);
}
// eslint-disable-next-line no-empty
} catch (error) {}
}
const btn = elm.querySelector('.MuiCode-copy') as HTMLButtonElement | null;
if (btn) {
const keyNode = btn.querySelector('.MuiCode-copyKeypress')?.childNodes[1];
if (!keyNode) {
// skip the logic if the btn is not generated from the markdown.
return;
}
keyNode.textContent = keyNode?.textContent?.replace('$key', key) || null;
btn.addEventListener('click', handleClick);
listeners.push(() => btn.removeEventListener('click', handleClick));
}
});
return () => {
listeners.forEach((removeEventListener) => {
removeEventListener();
});
};
}
return undefined;
}, [rootNode, router.pathname]);
return null;
}
function hasNativeSelection(element: HTMLTextAreaElement) {
if (window.getSelection()?.toString()) {
return true;
}
// window.getSelection() returns an empty string in Firefox for selections inside a form element.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=85686.
// Instead, we can use element.selectionStart that is only defined on form elements.
if (element && (element.selectionEnd || 0) - (element.selectionStart || 0) > 0) {
return true;
}
return false;
}
interface CodeCopyProviderProps {
children: React.ReactNode;
}
/**
* Place <CodeCopyProvider> at the page level. It will check the keydown event and try to initiate copy click if rootNode exist.
* Any code block inside the tree can set the rootNode when mouse enter to leverage keyboard copy.
*/
export function CodeCopyProvider({ children }: CodeCopyProviderProps) {
const rootNode = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
document.addEventListener('keydown', (event) => {
if (!rootNode.current) {
return;
}
// Skip if user is highlighting a text.
if (hasNativeSelection(event.target as HTMLTextAreaElement)) {
return;
}
// Skip if it's not a copy keyboard event
if (
!(
(event.ctrlKey || event.metaKey) &&
String.fromCharCode(event.keyCode) === 'C' &&
!event.shiftKey &&
!event.altKey
)
) {
return;
}
const copyBtn = rootNode.current.querySelector('.MuiCode-copy') as HTMLButtonElement;
const initialEventAction = copyBtn.getAttribute('data-ga-event-action');
// update the 'data-ga-event-action' on the button to track keyboard interaction
copyBtn.dataset.gaEventAction =
initialEventAction?.replace('click', 'keyboard') || 'copy-keyboard';
copyBtn.click(); // let the GA setup in GoogleAnalytics.js do the job
copyBtn.dataset.gaEventAction = initialEventAction!; // reset the 'data-ga-event-action' back to initial
});
}, []);
return (
<CodeBlockContext.Provider value={rootNode}>
<InitCodeCopy />
{children}
</CodeBlockContext.Provider>
);
}

View File

@@ -0,0 +1,33 @@
import useClipboardCopy from './useClipboardCopy';
export interface CodeCopyButtonProps {
code: string;
}
export function CodeCopyButton(props: CodeCopyButtonProps) {
const { code, ...other } = props;
const { copy, isCopied } = useClipboardCopy();
// This component is designed to be wrapped in NoSsr
const macOS = window.navigator.platform.toUpperCase().includes('MAC');
const key = macOS ? '⌘' : 'Ctrl + ';
return (
<div className="MuiCode-copy-container">
<button
{...other}
aria-label="Copy the code"
type="button"
className="MuiCode-copy"
onClick={async () => {
// event.stopPropagation();
await copy(code);
}}
>
{isCopied ? 'Copied' : 'Copy'}
<span className="MuiCode-copyKeypress" style={{ opacity: isCopied ? 0 : 1 }}>
<span>(or</span> {key}C<span>)</span>
</span>
</button>
</div>
);
}

View File

@@ -0,0 +1,3 @@
export * from './CodeCopy';
export * from './CodeCopyButton';
export { default as useClipboardCopy } from './useClipboardCopy';

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import clipboardCopy from 'clipboard-copy';
export default function useClipboardCopy() {
const [isCopied, setIsCopied] = React.useState(false);
const timeout = React.useRef<ReturnType<typeof setTimeout>>(undefined);
React.useEffect(
() => () => {
clearTimeout(timeout.current);
},
[],
);
const copy = async (text: string) => {
await clipboardCopy(text);
setIsCopied(true);
clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
setIsCopied(false);
}, 1200);
};
return { copy, isCopied };
}

View File

@@ -0,0 +1,220 @@
import * as React from 'react';
import { useRouter } from 'next/router';
import Chip from '@mui/material/Chip';
import Tooltip from '@mui/material/Tooltip';
import ChatRounded from '@mui/icons-material/ChatRounded';
import GitHubIcon from '@mui/icons-material/GitHub';
import { styled } from '@mui/material/styles';
import { MarkdownHeaders } from '@mui/internal-markdown';
import MarkdownIcon from '../svgIcons/MarkdownIcon';
import SketchIcon from '../svgIcons/SketchIcon';
import FigmaIcon from '../svgIcons/FigmaIcon';
import BundleSizeIcon from '../svgIcons/BundleSizeIcon';
import W3CIcon from '../svgIcons/W3CIcon';
import MaterialDesignIcon from '../svgIcons/MaterialDesignIcon';
import { useTranslate } from '../i18n';
const Root = styled('ul')(({ theme }) => ({
margin: theme.spacing(2, 0),
padding: 0,
listStyle: 'none',
display: 'flex',
flexWrap: 'wrap',
gap: 8,
'& .MuiChip-root': {
height: 26,
padding: '0 8px',
gap: 6,
'& .MuiChip-label': { padding: 0 },
'& .MuiChip-iconSmall': {
margin: 0,
fontSize: 14,
},
},
}));
const defaultPackageNames: Record<string, string | undefined> = {
'material-ui': '@mui/material',
'joy-ui': '@mui/joy',
'base-ui': '@mui/base',
system: '@mui/system',
};
export interface ComponentLinkHeaderProps {
design?: boolean;
markdown: {
headers: MarkdownHeaders;
};
}
export function ComponentLinkHeader(props: ComponentLinkHeaderProps) {
const {
markdown: { headers },
design,
} = props;
const t = useTranslate();
const router = useRouter();
const packageName =
headers.packageName ?? defaultPackageNames[headers.productId] ?? '@mui/material';
return (
<Root>
{packageName === '@mui/material' && (
<li>
<Chip
clickable
role={undefined}
component="a"
size="small"
variant="outlined"
href={`${router.pathname}.md`}
icon={<MarkdownIcon />}
data-ga-event-category="ComponentLinkHeader"
data-ga-event-action="click"
data-ga-event-label="Markdown"
data-ga-event-split="0.1"
label="View as Markdown"
/>
</li>
)}
{headers.githubLabel ? (
<li>
<Chip
clickable
role={undefined}
component="a"
size="small"
variant="outlined"
rel="nofollow"
href={`${process.env.SOURCE_CODE_REPO}/labels/${encodeURIComponent(
headers.githubLabel,
)}`}
icon={<ChatRounded color="primary" />}
data-ga-event-category="ComponentLinkHeader"
data-ga-event-action="click"
data-ga-event-label={t('githubLabel')}
data-ga-event-split="0.1"
label={t('githubLabel')}
/>
</li>
) : null}
<li>
<Tooltip title={t('bundleSizeTooltip')} describeChild>
<Chip
clickable
role={undefined}
component="a"
size="small"
variant="outlined"
rel="nofollow"
href={`https://bundlephobia.com/package/${packageName}@latest`}
icon={<BundleSizeIcon color="primary" />}
data-ga-event-category="ComponentLinkHeader"
data-ga-event-action="click"
data-ga-event-label={t('bundleSize')}
data-ga-event-split="0.1"
label={t('bundleSize')}
/>
</Tooltip>
</li>
{headers.githubSource ? (
<li>
<Chip
clickable
role={undefined}
component="a"
size="small"
variant="outlined"
rel="nofollow"
href={`${process.env.SOURCE_CODE_REPO}/tree/v${process.env.LIB_VERSION}/${headers.githubSource}`}
icon={<GitHubIcon />}
data-ga-event-category="ComponentLinkHeader"
data-ga-event-action="click"
data-ga-event-label="Source"
data-ga-event-split="0.1"
label="Source"
/>
</li>
) : null}
{headers.waiAria ? (
<li>
<Chip
clickable
role={undefined}
component="a"
size="small"
variant="outlined"
rel="nofollow"
href={headers.waiAria}
icon={<W3CIcon color="primary" />}
data-ga-event-category="ComponentLinkHeader"
data-ga-event-action="click"
data-ga-event-label="WAI-ARIA"
data-ga-event-split="0.1"
label="WAI-ARIA"
/>
</li>
) : null}
{headers.materialDesign ? (
<li>
<Chip
clickable
role={undefined}
component="a"
size="small"
variant="outlined"
rel="nofollow"
href={headers.materialDesign}
icon={<MaterialDesignIcon />}
data-ga-event-category="ComponentLinkHeader"
data-ga-event-action="click"
data-ga-event-label="Material Design"
data-ga-event-split="0.1"
label="Material Design"
/>
</li>
) : null}
{design === false ? null : (
<React.Fragment>
<li>
<Chip
clickable
role={undefined}
component="a"
size="small"
variant="outlined"
rel="nofollow"
href="https://mui.com/store/items/figma-react/?utm_source=docs&utm_medium=referral&utm_campaign=component-link-header"
icon={<FigmaIcon />}
data-ga-event-category="ComponentLinkHeader"
data-ga-event-action="click"
data-ga-event-label="Figma"
data-ga-event-split="0.1"
label="Figma"
/>
</li>
{packageName === '@mui/joy' ? null : (
<li>
<Chip
clickable
role={undefined}
component="a"
size="small"
variant="outlined"
rel="nofollow"
href="https://mui.com/store/items/sketch-react/?utm_source=docs&utm_medium=referral&utm_campaign=component-link-header"
icon={<SketchIcon />}
data-ga-event-category="ComponentLinkHeader"
data-ga-event-action="click"
data-ga-event-label="Sketch"
data-ga-event-split="0.1"
label="Sketch"
/>
</li>
)}
</React.Fragment>
)}
</Root>
);
}

View File

@@ -0,0 +1,2 @@
export * from './ComponentLinkHeader';
export { ComponentLinkHeader as default } from './ComponentLinkHeader';

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import { Translations, UserLanguageProvider } from '../i18n';
import { AdConfig, AdProvider } from '../Ad';
export interface DocsConfig {
LANGUAGES: string[];
LANGUAGES_SSR: string[];
LANGUAGES_IN_PROGRESS: string[];
LANGUAGES_IGNORE_PAGES: (pathname: string) => boolean;
}
const DocsConfigContext = React.createContext<DocsConfig | null>(null);
export interface DocsProviderProps {
config: DocsConfig;
adConfig?: Partial<AdConfig>;
defaultUserLanguage: string;
children?: React.ReactNode;
translations?: Translations;
}
export function DocsProvider({
config,
adConfig,
defaultUserLanguage,
translations,
children,
}: DocsProviderProps) {
return (
<DocsConfigContext.Provider value={config}>
<AdProvider config={adConfig}>
<UserLanguageProvider defaultUserLanguage={defaultUserLanguage} translations={translations}>
{children}
</UserLanguageProvider>
</AdProvider>
</DocsConfigContext.Provider>
);
}
export function useDocsConfig() {
const config = React.useContext(DocsConfigContext);
if (!config) {
throw new Error(
'Could not find docs config context value; please ensure the component is wrapped in a <DocsProvider>',
);
}
return config;
}

View File

@@ -0,0 +1 @@
export * from './DocsProvider';

View File

@@ -0,0 +1,49 @@
import { expect } from 'chai';
import { createRenderer } from '@mui/internal-test-utils';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { getDesignTokens } from '../branding';
import { HighlightedCode } from './HighlightedCode';
describe('HighlightedCode', () => {
const { render } = createRenderer();
it('does not crash with default theme', () => {
expect(() =>
render(
<ThemeProvider theme={createTheme()}>
<HighlightedCode code="" language="javascript" />
</ThemeProvider>,
),
).not.to.throw();
});
it('does not crash with default theme in dark mode', () => {
expect(() =>
render(
<ThemeProvider theme={createTheme({ palette: { mode: 'dark' } })}>
<HighlightedCode code="" language="javascript" />
</ThemeProvider>,
),
).not.to.throw();
});
it('does not crash with branding theme', () => {
expect(() =>
render(
<ThemeProvider theme={createTheme(getDesignTokens('light'))}>
<HighlightedCode code="" language="javascript" />
</ThemeProvider>,
),
).not.to.throw();
});
it('does not crash with branding theme in dark mode', () => {
expect(() =>
render(
<ThemeProvider theme={createTheme(getDesignTokens('dark'))}>
<HighlightedCode code="" language="javascript" />
</ThemeProvider>,
),
).not.to.throw();
});
});

View File

@@ -0,0 +1,71 @@
import * as React from 'react';
import prism from '@mui/internal-markdown/prism';
import { NoSsr } from '@mui/base/NoSsr';
import { ButtonProps } from '@mui/material/Button';
import { SxProps, styled } from '@mui/material/styles';
import { useCodeCopy, CodeCopyButton } from '../CodeCopy';
import { MarkdownElement } from '../MarkdownElement';
const Pre = styled('pre')(({ theme }) => ({
margin: 0,
color: 'hsl(60deg 30% 96.08%)', // fallback color until Prism's theme is loaded
WebkitOverflowScrolling: 'touch', // iOS momentum scrolling.
'& code': {
// Avoid layout jump after hydration (style injected by Prism)
...theme.typography.caption,
fontFamily: theme.typography.fontFamilyCode,
fontWeight: 400,
WebkitFontSmoothing: 'subpixel-antialiased',
// Reset for Safari
// https://github.com/necolas/normalize.css/blob/master/normalize.css#L102
},
}));
export interface HighlightedCodeProps {
code: string;
copyButtonHidden?: boolean;
copyButtonProps?: ButtonProps;
language: string;
parentComponent?: React.ElementType;
plainStyle?: boolean;
preComponent?: React.ElementType;
sx?: SxProps;
}
export function HighlightedCode(props: HighlightedCodeProps) {
const {
code,
copyButtonHidden = false,
copyButtonProps,
language,
plainStyle,
parentComponent: Component = plainStyle ? React.Fragment : MarkdownElement,
preComponent: PreComponent = plainStyle ? Pre : 'pre',
...other
} = props;
const renderedCode = React.useMemo(() => {
return prism(code.trim(), language);
}, [code, language]);
const handlers = useCodeCopy();
const componentProps = !plainStyle ? other : undefined;
return (
<Component {...componentProps}>
<div className="MuiCode-root" {...handlers} style={{ height: '100%' }}>
{copyButtonHidden ? null : (
<NoSsr>
<CodeCopyButton code={code} {...copyButtonProps} />
</NoSsr>
)}
<PreComponent>
<code
className={`language-${language}`}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: renderedCode }}
/>
</PreComponent>
</div>
</Component>
);
}

View File

@@ -0,0 +1 @@
export * from './HighlightedCode';

View File

@@ -0,0 +1,345 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import { Tabs, TabsOwnProps } from '@mui/base/Tabs';
import { TabsList as TabsListBase } from '@mui/base/TabsList';
import { TabPanel as TabPanelBase } from '@mui/base/TabPanel';
import { Tab as TabBase } from '@mui/base/Tab';
import useLocalStorageState from '@mui/utils/useLocalStorageState';
import { HighlightedCode } from '../HighlightedCode';
const PACKAGE_MANAGER_ORDER = new Map(
['npm', 'pnpm', 'yarn'].map((manager, index) => [manager, index]),
);
export const CodeTabList = styled(TabsListBase)<{
ownerState: { mounted: boolean; contained?: boolean };
}>(({ theme }) => ({
display: 'flex',
gap: theme.spacing(0.5),
borderLeft: '1px solid',
borderRight: '1px solid',
overflowX: 'auto',
...theme.applyDarkStyles({
backgroundColor: alpha(theme.palette.primaryDark[800], 0.2),
}),
variants: [
{
props: ({ ownerState }) => ownerState?.contained,
style: {
padding: theme.spacing(1.5, 1),
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
padding: theme.spacing(1),
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
borderTop: 'none',
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
borderTop: '1px solid',
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
borderBottom: 'none',
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
borderBottom: '1px solid',
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
borderTopLeftRadius: 0,
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
borderTopLeftRadius: (theme.vars || theme).shape.borderRadius,
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
borderTopRightRadius: 0,
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
borderTopRightRadius: (theme.vars || theme).shape.borderRadius,
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
borderColor: (theme.vars || theme).palette.divider,
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
borderColor: (theme.vars || theme).palette.primaryDark[700],
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
backgroundColor: alpha(theme.palette.grey[50], 0.2),
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
backgroundColor: (theme.vars || theme).palette.primaryDark[900],
},
},
],
}));
export const CodeTabPanel = styled(TabPanelBase)<{
ownerState: { mounted: boolean; contained?: boolean };
}>({
'& pre': {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
'& code': {},
},
variants: [
{
props: ({ ownerState }) => ownerState?.contained,
style: {
marginTop: -1,
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
marginTop: 0,
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
'& pre': {
marginTop: 0,
},
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
'& pre': {
marginTop: -1,
},
},
},
{
props: ({ ownerState }) => ownerState.mounted,
style: {
'& pre': {
'& code': {
opacity: 1,
},
},
},
},
{
props: ({ ownerState }) => !ownerState.mounted,
style: {
'& pre': {
'& code': {
opacity: 0,
},
},
},
},
],
});
export const CodeTab = styled(TabBase)<{ ownerState: { mounted: boolean; contained?: boolean } }>(
({ theme }) => ({
variants: [
{
props: ({ ownerState }) => ownerState?.contained,
style: {
border: '1px solid transparent',
fontSize: theme.typography.pxToRem(13),
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
border: 'none',
fontSize: theme.typography.pxToRem(12),
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
color: (theme.vars || theme).palette.text.tertiary,
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
color: (theme.vars || theme).palette.grey[500],
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
fontFamily: theme.typography.fontFamily,
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
fontFamily: theme.typography.fontFamilyCode,
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
fontWeight: theme.typography.fontWeightMedium,
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
fontWeight: theme.typography.fontWeightBold,
},
},
{
props: ({ ownerState }) => ownerState?.contained,
style: {
transition: 'background, color, 100ms ease',
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
transition: 'unset',
},
},
{
props: ({ ownerState }) => !ownerState?.contained,
style: {
'&:hover': {
backgroundColor: alpha(theme.palette.primaryDark[500], 0.5),
color: (theme.vars || theme).palette.grey[400],
},
},
},
{
props: ({ ownerState }) => !ownerState?.contained && ownerState.mounted,
style: {
'&.base--selected': {
color: '#FFF',
'&::after': {
content: "''",
position: 'absolute',
left: 0,
bottom: '-8px',
height: 2,
width: '100%',
bgcolor: (theme.vars || theme).palette.primary.light,
},
},
},
},
],
...theme.unstable_sx({
flex: '0 0 auto',
height: 26,
p: '2px 8px',
bgcolor: 'transparent',
lineHeight: 1.2,
outline: 'none',
minWidth: 45,
cursor: 'pointer',
borderRadius: 99,
position: 'relative',
'&:hover': {
backgroundColor: (theme.vars || theme).palette.divider,
},
'&:focus-visible': {
outline: '3px solid',
outlineOffset: '1px',
outlineColor: (theme.vars || theme).palette.primary.light,
},
}),
}),
);
type TabsConfig = {
code: string | ((tab: string) => string);
language: string;
tab: string;
};
export function HighlightedCodeWithTabs(
props: {
tabs: Array<TabsConfig>;
storageKey?: string;
} & Record<string, any>,
) {
const { tabs, storageKey } = props;
const availableTabs = React.useMemo(() => {
const result = tabs.map(({ tab }) => tab);
if (storageKey === 'package-manager') {
result.sort(
(a, b) =>
(PACKAGE_MANAGER_ORDER.get(a) ?? Infinity) - (PACKAGE_MANAGER_ORDER.get(b) ?? Infinity),
);
}
return result;
}, [storageKey, tabs]);
const [activeTab, setActiveTab] = useLocalStorageState(storageKey ?? null, availableTabs[0]);
// During hydration, activeTab is null, default to first value.
const defaultizedActiveTab = activeTab ?? availableTabs[0];
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
const handleChange: TabsOwnProps['onChange'] = (event, newValue) => {
setActiveTab(newValue as string);
};
const ownerState = { mounted };
return (
<Tabs selectionFollowsFocus value={defaultizedActiveTab} onChange={handleChange}>
<CodeTabList ownerState={ownerState}>
{tabs.map(({ tab }) => (
<CodeTab ownerState={ownerState} key={tab} value={tab}>
{tab}
</CodeTab>
))}
</CodeTabList>
{tabs.map(({ tab, language, code }) => (
<CodeTabPanel ownerState={ownerState} key={tab} value={tab}>
<HighlightedCode
language={language || 'bash'}
code={typeof code === 'function' ? code(tab) : code}
/>
</CodeTabPanel>
))}
</Tabs>
);
}

View File

@@ -0,0 +1,2 @@
export * from './HighlightedCodeWithTabs';
export { HighlightedCodeWithTabs as default } from './HighlightedCodeWithTabs';

View File

@@ -0,0 +1,109 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Typography, { TypographyProps } from '@mui/material/Typography';
import { Link, LinkProps } from '../Link';
interface GlowingIconContainerProps {
icon: React.ReactNode;
}
export function GlowingIconContainer({ icon }: GlowingIconContainerProps) {
return (
<Box
sx={(theme) => ({
width: 36,
height: 36,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexShrink: 0,
borderRadius: 1,
border: '1px solid',
borderColor: 'primary.200',
bgcolor: 'primary.50',
boxShadow: `0px 0 0 2px ${alpha(theme.palette.primary[500], 0.1)}, 0px 2px 12px 0px rgba(234, 237, 241, 0.3) inset`,
'& .MuiSvgIcon-root': {
fontSize: theme.typography.pxToRem(18),
},
...theme.applyDarkStyles({
borderColor: alpha(theme.palette.primary[400], 0.25),
bgcolor: alpha(theme.palette.primary[900], 0.2),
boxShadow: `0 0 0 2px ${alpha(theme.palette.primary[600], 0.1)}, 0px 2px 12px 0px rgba(0, 0, 0, 0.25) inset`,
}),
})}
>
{icon}
</Box>
);
}
interface InfoCardProps {
classNameDescription?: string;
classNameTitle?: string;
description?: string;
icon?: React.ReactNode;
link?: string;
prefetch?: LinkProps['prefetch'];
svg?: React.ReactNode;
title: string;
titleProps?: TypographyProps;
}
export function InfoCard(props: InfoCardProps) {
const {
classNameDescription,
classNameTitle,
description,
icon,
link,
svg,
title,
titleProps,
...other
} = props;
return (
<Paper
variant="outlined"
component={link ? Link : 'div'}
href={link}
{...(link
? {
noLinkStyle: true,
// Fix overloading with prefetch={false}, only prefetch on hover.
prefetch: false,
}
: {})}
sx={(theme) => ({
p: 2.5,
height: '100%',
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
...theme.applyDarkStyles({
bgcolor: alpha(theme.palette.primaryDark[800], 0.25),
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
borderColor: 'primaryDark.700',
}),
})}
{...other}
>
{svg && svg}
{icon && <GlowingIconContainer icon={icon} />}
<Typography
fontWeight="semiBold"
component="h3"
color="text.primary"
variant="body2"
mt={icon ? 2 : 0}
mb={description ? 0.5 : 0}
className={classNameTitle}
{...titleProps}
>
{title}
</Typography>
<Typography variant="body2" color="text.secondary" className={classNameDescription}>
{description}
</Typography>
</Paper>
);
}

View File

@@ -0,0 +1 @@
export * from './InfoCard';

View File

@@ -0,0 +1,96 @@
import * as React from 'react';
import clsx from 'clsx';
import { useRouter } from 'next/router';
import NextLink, { LinkProps as NextLinkProps } from 'next/link';
import MuiLink, { LinkProps as MuiLinkProps } from '@mui/material/Link';
import { useUserLanguage } from '../i18n';
import { useDocsConfig } from '../DocsProvider';
/**
* File to keep in sync with:
*
* - /packages/mui-docs/src/Link/Link.tsx
* - /examples/material-ui-nextjs-pages-router/src/Link.js
* - /examples/material-ui-nextjs-pages-router-ts/src/Link.tsx
* - /examples/material-ui-nextjs-ts-v4-v5-migration/src/Link.tsx
*/
interface NextLinkComposedProps
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>,
Omit<NextLinkProps, 'href' | 'as' | 'passHref' | 'onMouseEnter' | 'onClick' | 'onTouchStart'> {
to: NextLinkProps['href'];
linkAs?: NextLinkProps['as'];
}
export const NextLinkComposed = React.forwardRef<HTMLAnchorElement, NextLinkComposedProps>(
function NextLinkComposed(props, ref) {
const { to, linkAs, ...other } = props;
return <NextLink href={to} as={linkAs} data-no-markdown-link="true" ref={ref} {...other} />;
},
);
export type LinkProps = {
activeClassName?: string;
as?: NextLinkProps['as'];
href: NextLinkProps['href'];
linkAs?: NextLinkProps['as']; // Useful when the as prop is shallow by styled().
noLinkStyle?: boolean;
} & Omit<NextLinkComposedProps, 'to' | 'linkAs' | 'href'> &
Omit<MuiLinkProps, 'href'>;
// A styled version of the Next.js Link component:
// https://nextjs.org/docs/pages/api-reference/components/link
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link(props, ref) {
const {
activeClassName = 'active',
as,
className: classNameProps,
href,
linkAs: linkAsProp,
noLinkStyle,
...other
} = props;
const router = useRouter();
const pathname = typeof href === 'string' ? href : href?.pathname;
const routerPathname = router.pathname.replace('/[docsTab]', '');
const className = clsx(classNameProps, {
[activeClassName]: routerPathname === pathname && activeClassName,
});
const userLanguage = useUserLanguage();
const { LANGUAGES_IGNORE_PAGES } = useDocsConfig();
let linkAs = linkAsProp || as || (href as string);
if (
userLanguage !== 'en' &&
pathname &&
pathname.startsWith('/') &&
!LANGUAGES_IGNORE_PAGES(pathname) &&
!pathname.startsWith(`/${userLanguage}/`)
) {
linkAs = `/${userLanguage}${linkAs}`;
}
const nextjsProps = {
to: href,
linkAs,
};
if (noLinkStyle) {
return <NextLinkComposed className={className} ref={ref} {...nextjsProps} {...other} />;
}
return (
<MuiLink
component={NextLinkComposed}
className={className}
ref={ref}
{...nextjsProps}
{...other}
/>
);
});

View File

@@ -0,0 +1 @@
export * from './Link';

View File

@@ -0,0 +1,856 @@
import * as React from 'react';
import clsx from 'clsx';
import { alpha, darken, styled } from '@mui/material/styles';
import useForkRef from '@mui/utils/useForkRef';
import { brandingDarkTheme as darkTheme, brandingLightTheme as lightTheme } from '../branding';
const Root = styled('div')(
({ theme }) => ({
...lightTheme.typography.body1,
lineHeight: 1.625, // Rounds up to 26pxincreased compared to the 1.5 default to make the docs easier to read.
color: `var(--muidocs-palette-text-primary, ${lightTheme.palette.text.primary})`,
'& :focus-visible': {
outline: `3px solid ${alpha(lightTheme.palette.primary[500], 0.5)}`,
outlineOffset: 2,
},
'& strong': {
color: `var(--muidocs-palette-text-primary, ${lightTheme.palette.text.primary})`,
},
wordBreak: 'break-word',
'& pre': {
lineHeight: 1.5, // Developers like when the code is dense.
margin: theme.spacing(2, 'auto'),
padding: theme.spacing(2),
backgroundColor: 'hsl(210, 25%, 9%)', // a special, one-off, color tailored for the code blocks using MUI's branding theme blue palette as the starting point. It has a less saturaded color but still maintaining a bit of the blue tint.
color: 'hsl(60, 30%, 96%)',
colorScheme: 'dark',
borderRadius: `var(--muidocs-shape-borderRadius, ${
theme.shape?.borderRadius ?? lightTheme.shape.borderRadius
}px)`,
border: '1px solid',
borderColor: `var(--muidocs-palette-primaryDark-700, ${lightTheme.palette.primaryDark[700]})`,
overflow: 'auto',
WebkitOverflowScrolling: 'touch',
fontSize: lightTheme.typography.pxToRem(13),
maxHeight: '400px',
},
'& code': {
...lightTheme.typography.body2,
fontFamily: lightTheme.typography.fontFamilyCode,
fontWeight: 400,
WebkitFontSmoothing: 'subpixel-antialiased',
},
'& pre > code': {
// Reset for Safari
// https://github.com/necolas/normalize.css/blob/master/normalize.css#L102
fontSize: 'inherit',
},
// inline code block
'& :not(pre) > code': {
padding: '2px 4px',
color: `var(--muidocs-palette-text-primary, ${lightTheme.palette.text.primary})`,
backgroundColor: `var(--muidocs-palette-grey-50, ${lightTheme.palette.grey[50]})`,
border: '1px solid',
borderColor: `var(--muidocs-palette-grey-200, ${lightTheme.palette.grey[200]})`,
borderRadius: 6,
fontSize: lightTheme.typography.pxToRem(13),
direction: 'ltr /*! @noflip */',
boxDecorationBreak: 'clone',
},
'& h1': {
...lightTheme.typography.h3,
fontSize: lightTheme.typography.pxToRem(36),
fontFamily: `"General Sans", ${lightTheme.typography.fontFamilySystem}`,
margin: '10px 0',
color: `var(--muidocs-palette-primaryDark-900, ${lightTheme.palette.primaryDark[900]})`,
fontWeight: 600,
letterSpacing: -0.2,
},
'& .description': {
...lightTheme.typography.subtitle1,
fontWeight: 400,
margin: '0 0 24px',
},
'& .component-tabs': {
margin: '0 0 40px',
},
'& h2': {
...lightTheme.typography.h5,
fontFamily: `"General Sans", ${lightTheme.typography.fontFamilySystem}`,
fontSize: theme.typography.pxToRem(26),
fontWeight: lightTheme.typography.fontWeightSemiBold,
color: `var(--muidocs-palette-grey-900, ${lightTheme.palette.grey[900]})`,
margin: '40px 0 4px',
},
'& h3': {
...lightTheme.typography.h6,
fontFamily: `"General Sans", ${lightTheme.typography.fontFamilySystem}`,
fontSize: theme.typography.pxToRem(20),
fontWeight: lightTheme.typography.fontWeightSemiBold,
color: `var(--muidocs-palette-grey-900, ${lightTheme.palette.grey[900]})`,
margin: '24px 0 4px',
},
'& h4': {
...lightTheme.typography.subtitle1,
fontFamily: `"General Sans", ${lightTheme.typography.fontFamilySystem}`,
fontWeight: lightTheme.typography.fontWeightSemiBold,
color: `var(--muidocs-palette-grey-900, ${lightTheme.palette.grey[900]})`,
margin: '20px 0 6px',
},
'& h5': {
...lightTheme.typography.subtitle2,
fontFamily: `"General Sans", ${lightTheme.typography.fontFamilySystem}`,
fontWeight: lightTheme.typography.fontWeightSemiBold,
color: `var(--muidocs-palette-grey-900, ${lightTheme.palette.grey[900]})`,
margin: '20px 0 8px',
},
'& p': {
marginTop: 0,
marginBottom: 16,
color: `var(--muidocs-palette-grey-900, ${lightTheme.palette.grey[900]})`,
},
'& ul, & ol': {
paddingLeft: 30,
marginTop: 0,
marginBottom: 16,
'& ul, & ol': {
marginBottom: 6,
},
},
'& a[target="_blank"]::after': {
content: '""',
maskImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' focusable='false' aria-hidden='true' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M6 6v2h8.59L5 17.59 6.41 19 16 9.41V18h2V6z'%3E%3C/path%3E%3C/svg%3E")`,
display: 'inline-flex',
width: '1em',
height: '1em',
color: 'inherit',
backgroundColor: 'currentColor',
transform: 'translate(0, 2px)',
transition: 'transform 0.3s cubic-bezier(0.1, 0.8, 0.3, 1)', // bounce effect
opacity: 0.8,
},
'& a[target="_blank"]:hover::after': {
opacity: 1,
transform: 'translate(1px, 0)',
},
'& a.remove-link-arrow::after': {
// Allows to remove link arrows for images
display: 'none',
},
'& .ad.description a::after': {
// Remove link arrow for ads
display: 'none',
},
'& a': {
// Style taken from the Link component
color: `var(--muidocs-palette-primary-600, ${lightTheme.palette.primary[600]})`,
fontWeight: theme.typography.fontWeightMedium,
textDecoration: 'underline',
textDecorationColor: alpha(lightTheme.palette.primary.main, 0.4),
'&:hover': {
textDecorationColor: 'inherit',
},
},
'& a code': {
color: darken(lightTheme.palette.primary.main, 0.2),
},
'& h1, & h2, & h3, & h4': {
display: 'flex',
alignItems: 'center',
gap: 6,
'& code': {
fontSize: 'inherit',
lineHeight: 'inherit',
// Remove scroll on small screens.
wordBreak: 'break-all',
},
'& .title-link-to-anchor': {
color: 'inherit',
textDecoration: 'none',
boxShadow: 'none',
fontWeight: 'inherit',
position: 'relative',
userSelect: 'text',
},
'& .anchor-icon': {
// To prevent the link to get the focus.
display: 'inline-flex',
alignItems: 'center',
visibility: 'hidden',
},
'& .anchor-icon, & .comment-link': {
padding: 0,
cursor: 'pointer',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
textAlign: 'center',
marginLeft: 8,
height: 26,
width: 26,
color: `var(--muidocs-palette-grey-600, ${lightTheme.palette.grey[600]})`,
backgroundColor: 'transparent',
border: '1px solid transparent',
borderRadius: 8,
transition: theme.transitions.create(
['visibility', 'background-color', 'color', 'border-color'],
{
duration: theme.transitions.duration.shortest,
},
),
'&:hover': {
backgroundColor: alpha(lightTheme.palette.primary[100], 0.4),
borderColor: `var(--muidocs-palette-primary-100, ${lightTheme.palette.primary[100]})`,
color: `var(--muidocs-palette-primary-main, ${lightTheme.palette.primary.main})`,
},
'& svg': {
height: 14,
width: 14,
fill: 'currentColor',
pointerEvents: 'none',
verticalAlign: 'middle',
},
},
'&:hover .anchor-icon': {
visibility: 'visible',
},
'& .comment-link': {
display: 'none', // So we can have the comment button opt-in.
marginLeft: 'auto',
transition: theme.transitions.create(['background-color', 'color', 'border-color'], {
duration: theme.transitions.duration.shortest,
}),
'& svg': {
fill: 'currentColor',
marginRight: 1.5,
},
},
},
'& h1 code, & h2 code, & h3 code': {
color: `var(--muidocs-palette-grey-900, ${lightTheme.palette.grey[900]})`,
},
'& h1 code': {
fontWeight: lightTheme.typography.fontWeightSemiBold,
},
'& h2 code': {
fontSize: lightTheme.typography.pxToRem(24),
fontWeight: lightTheme.typography.fontWeightSemiBold,
},
'& h3 code': {
fontSize: lightTheme.typography.pxToRem(18),
},
'& table': {
// Trade display table for scroll overflow
display: 'block',
wordBreak: 'normal',
overflowX: 'auto',
WebkitOverflowScrolling: 'touch',
borderCollapse: 'collapse',
marginBottom: '20px',
borderSpacing: 0,
'& .prop-name, & .prop-type, & .prop-default, & .slot-name, & .slot-defaultClass, & .slot-default':
{
fontWeight: 400,
fontFamily: lightTheme.typography.fontFamilyCode,
WebkitFontSmoothing: 'subpixel-antialiased',
fontSize: lightTheme.typography.pxToRem(13),
},
'& .required': {
color: '#006500',
},
'& .optional': {
color: '#45529f',
},
'& .prop-type, & .slot-defaultClass': {
color: '#932981',
},
'& .prop-default, & .slot-default': {
borderBottom: `1px dotted var(--muidocs-palette-divider, ${lightTheme.palette.divider})`,
},
},
'& td': {
...theme.typography.body2,
borderBottom: `1px solid var(--muidocs-palette-divider, ${lightTheme.palette.divider})`,
paddingRight: 20,
paddingTop: 16,
paddingBottom: 16,
color: `var(--muidocs-palette-text-secondary, ${lightTheme.palette.text.secondary})`,
},
'& td code': {
lineHeight: 1.6,
},
'& th': {
fontSize: theme.typography.pxToRem(14),
lineHeight: theme.typography.pxToRem(24),
fontWeight: 500,
color: `var(--muidocs-palette-text-primary, ${lightTheme.palette.text.primary})`,
whiteSpace: 'pre',
borderBottom: `1px solid var(--muidocs-palette-divider, ${lightTheme.palette.divider})`,
paddingRight: 20,
paddingTop: 12,
paddingBottom: 12,
},
'& blockquote': {
position: 'relative',
padding: '0 16px',
margin: 0,
borderLeft: '1.5px solid',
borderColor: `var(--muidocs-palette-grey-200, ${lightTheme.palette.grey[200]})`,
'& p': {
fontSize: theme.typography.pxToRem(12.5),
fontFamily: lightTheme.typography.fontFamilyCode,
fontWeight: lightTheme.typography.fontWeightMedium,
lineHeight: theme.typography.pxToRem(24),
textIndent: 20,
},
'&::before': {
position: 'absolute',
// eslint-disable-next-line material-ui/straight-quotes
content: '"“"',
color: `var(--muidocs-palette-grey-300, ${lightTheme.palette.grey[300]})`,
fontSize: '2.5rem',
top: 8,
marginLeft: -6,
lineHeight: 0.5,
},
},
'& .MuiCallout-root': {
display: 'flex',
gap: '8px',
padding: '12px',
margin: '16px 0',
border: '1px solid',
color: `var(--muidocs-palette-text-secondary, ${lightTheme.palette.text.secondary})`,
borderColor: `var(--muidocs-palette-grey-100, ${lightTheme.palette.grey[100]})`,
borderRadius: `var(--muidocs-shape-borderRadius, ${
theme.shape?.borderRadius ?? lightTheme.shape.borderRadius
}px)`,
'& .MuiCallout-content': {
minWidth: 0, // Allows content to shrink. Useful when callout contains code block
flexGrow: 1,
},
'& code': {
height: 'fit-content',
backgroundColor: `var(--muidocs-palette-grey-100, ${lightTheme.palette.grey[100]})`,
borderColor: `var(--muidocs-palette-grey-300, ${lightTheme.palette.grey[300]})`,
},
'& p': {
marginBottom: '8px',
'& > p:last-child, & > ul:last-child': {
// Avoid margin on last child
marginBottom: 0,
},
'& > ul': {
// Because of the gap left by the icon, we visually need less padding
paddingLeft: 22,
},
},
'& .MuiCode-root': {
'& pre': {
margin: '4px 0 0 0',
borderRadius: '12px 12px 6px 12px',
borderColor: alpha(lightTheme.palette.primaryDark[600], 0.6),
'& code': {
backgroundColor: 'transparent',
},
},
},
'& .MuiCallout-icon-container': {
width: 26, // to match text's line-height
height: 26,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
'& svg': {
width: 18,
height: 18,
},
},
'& ul, & p': {
'&:last-child': {
margin: 0,
},
},
'& ul, li, p': {
color: 'inherit',
},
'&.MuiCallout-error': {
color: `var(--muidocs-palette-error-900, ${lightTheme.palette.error[900]})`,
backgroundColor: `var(--muidocs-palette-error-50, ${lightTheme.palette.error[50]})`,
borderColor: `var(--muidocs-palette-error-100, ${lightTheme.palette.error[100]})`,
'& strong': {
color: `var(--muidocs-palette-error-800, ${lightTheme.palette.error[800]})`,
},
'& svg': {
fill: `var(--muidocs-palette-error-500, ${lightTheme.palette.error[600]})`,
},
'& a': {
color: `var(--muidocs-palette-error-800, ${lightTheme.palette.error[800]})`,
textDecorationColor: alpha(lightTheme.palette.error.main, 0.4),
'&:hover': {
textDecorationColor: 'inherit',
},
},
},
'&.MuiCallout-info': {
color: `var(--muidocs-palette-grey-900, ${lightTheme.palette.grey[900]})`,
backgroundColor: `var(--muidocs-palette-grey-50, ${lightTheme.palette.grey[50]})`,
borderColor: `var(--muidocs-palette-grey-100, ${lightTheme.palette.grey[100]})`,
'& strong': {
color: `var(--muidocs-palette-primary-800, ${lightTheme.palette.primary[800]})`,
},
'& svg': {
fill: `var(--muidocs-palette-grey-600, ${lightTheme.palette.grey[600]})`,
},
},
'&.MuiCallout-success': {
color: `var(--muidocs-palette-success-900, ${lightTheme.palette.success[900]})`,
backgroundColor: `var(--muidocs-palette-success-50, ${lightTheme.palette.success[50]})`,
borderColor: `var(--muidocs-palette-success-100, ${lightTheme.palette.success[100]})`,
'& strong': {
color: `var(--muidocs-palette-success-900, ${lightTheme.palette.success[900]})`,
},
'& svg': {
fill: `var(--muidocs-palette-success-600, ${lightTheme.palette.success[600]})`,
},
'& a': {
color: `var(--muidocs-palette-success-900, ${lightTheme.palette.success[900]})`,
textDecorationColor: alpha(lightTheme.palette.success.main, 0.4),
'&:hover': {
textDecorationColor: 'inherit',
},
},
},
'&.MuiCallout-warning': {
color: `var(--muidocs-palette-grey-900, ${lightTheme.palette.grey[900]})`,
backgroundColor: alpha(lightTheme.palette.warning[50], 0.5),
borderColor: alpha(lightTheme.palette.warning[700], 0.15),
'& strong': {
color: `var(--muidocs-palette-warning-800, ${lightTheme.palette.warning[800]})`,
},
'& svg': {
fill: `var(--muidocs-palette-warning-600, ${lightTheme.palette.warning[600]})`,
},
'& a': {
color: `var(--muidocs-palette-warning-800, ${lightTheme.palette.warning[800]})`,
textDecorationColor: alpha(lightTheme.palette.warning.main, 0.4),
'&:hover': {
textDecorationColor: 'inherit',
},
},
},
},
'& img, & video': {
// Use !important so that inline style on <img> or <video> can't win.
// This avoid horizontal overflows on mobile.
maxWidth: '100% !important',
// Avoid the image to be fixed height, so it can respect the aspect ratio.
height: 'auto',
},
'& img': {
// Avoid layout jump
display: 'inline-block',
// Avoid very sharp edges
borderRadius: 2,
},
'& hr': {
height: 1,
margin: theme.spacing(5, 0),
border: 0,
flexShrink: 0,
backgroundColor: `var(--muidocs-palette-divider, ${lightTheme.palette.divider})`,
},
'& kbd.key': {
padding: 6,
display: 'inline-block',
whiteSpace: 'nowrap',
margin: '0 1px',
fontFamily: lightTheme.typography.fontFamilyCode,
fontSize: lightTheme.typography.pxToRem(11),
color: `var(--muidocs-palette-text-primary, ${lightTheme.palette.text.primary})`,
lineHeight: '10px',
verticalAlign: 'middle',
borderRadius: 6,
border: `1px solid var(--muidocs-palette-grey-300, ${lightTheme.palette.grey[300]})`,
backgroundColor: `var(--muidocs-palette-grey-50, ${lightTheme.palette.grey[50]})`,
boxShadow: `inset 0 -2px 0 var(--muidocs-palette-grey-200, ${lightTheme.palette.grey[200]})`,
},
'& details': {
width: '100%',
padding: theme.spacing(1),
marginBottom: theme.spacing(1.5),
border: '1px solid',
borderColor: `var(--muidocs-palette-divider, ${lightTheme.palette.divider})`,
borderRadius: `var(--muidocs-shape-borderRadius, ${
theme.shape?.borderRadius ?? lightTheme.shape.borderRadius
}px)`,
'& pre': {
marginTop: theme.spacing(1),
},
},
'& summary': {
cursor: 'pointer',
padding: theme.spacing(1),
borderRadius: 6,
listStyleType: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
transition: theme.transitions.create(['background'], {
duration: theme.transitions.duration.shortest,
}),
':after': {
content: '""',
maskImage: `url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6L8 10L12 6' stroke='black' stroke-width='1.66667' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A")`,
display: 'inline-flex',
width: '1em',
height: '1em',
color: 'inherit',
backgroundColor: 'currentColor',
},
'&:hover': {
backgroundColor: `var(--muidocs-palette-grey-100, ${lightTheme.palette.grey[50]})`,
},
},
'& details[open] > summary::after': {
content: '""',
maskImage: `url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 10L8 6L4 10' stroke='black' stroke-width='1.66667' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A")`,
},
'& .MuiCode-root': {
direction: 'ltr /*! @noflip */',
position: 'relative',
// Font size reset to fix a bug with Safari 16.0 when letterSpacing is set
fontSize: 10,
'&:has(.MuiCode-title)': {
margin: theme.spacing(2, 'auto'),
border: `1px solid var(--muidocs-palette-primaryDark-700, ${lightTheme.palette.primaryDark[700]})`,
borderRadius: theme.shape.borderRadius,
overflow: 'clip',
'& .MuiCode-copy': {
top: '56px',
},
},
},
'& .MuiCode-copy-container': {
// This container is only used in demo and highlight code
position: 'sticky',
zIndex: 1,
top: 0,
},
'& .MuiCode-copy': {
cursor: 'pointer',
position: 'absolute',
top: 12,
right: 12,
display: 'inline-flex',
flexDirection: 'row-reverse',
alignItems: 'center',
padding: theme.spacing(0.5),
paddingBottom: '5px', // optical alignment
fontFamily: lightTheme.typography.fontFamily,
fontWeight: lightTheme.typography.fontWeightMedium,
fontSize: lightTheme.typography.pxToRem(12),
borderRadius: 6,
border: '1px solid',
borderColor: alpha(lightTheme.palette.primaryDark[600], 0.5),
backgroundColor: alpha(lightTheme.palette.primaryDark[800], 0.5),
color: `var(--muidocs-palette-grey-300, ${lightTheme.palette.grey[300]})`,
transition: theme.transitions.create(['background', 'borderColor', 'display'], {
duration: theme.transitions.duration.shortest,
}),
'@media (max-width: 640px)': {
display: 'none',
},
'& .MuiCode-copied-label': {
display: 'none',
},
'&:hover, &:focus': {
borderColor: `var(--muidocs-palette-primaryDark-400, ${lightTheme.palette.primaryDark[400]})`,
backgroundColor: `var(--muidocs-palette-primaryDark-700, ${lightTheme.palette.primaryDark[700]})`,
color: '#FFF',
'& .MuiCode-copyKeypress': {
display: 'block',
// Approximate no hover capabilities with no keyboard
// https://github.com/w3c/csswg-drafts/issues/3871
'@media (any-hover: none)': {
display: 'none',
},
},
},
'& .MuiCode-copyKeypress': {
display: 'none',
position: 'absolute',
right: 34,
},
'&[data-copied]': {
borderColor: `var(--muidocs-palette-primaryDark-400, ${lightTheme.palette.primaryDark[400]})`,
backgroundColor: `var(--muidocs-palette-primaryDark-700, ${lightTheme.palette.primaryDark[700]})`,
color: '#fff',
'& .MuiCode-copyKeypress': {
opacity: 0,
},
'& .MuiCode-copy-label': {
display: 'none',
},
'& .MuiCode-copied-label': {
display: 'block',
},
},
},
'& .MuiCode-copyKeypress': {
pointerEvents: 'none',
userSelect: 'none',
marginRight: theme.spacing(1.2),
marginBottom: theme.spacing(0.2),
whiteSpace: 'nowrap',
opacity: 0.6,
},
'& li': {
// tight lists https://spec.commonmark.org/0.30/#tight
marginBottom: 4,
'& pre': {
marginTop: theme.spacing(1),
},
// loose lists https://spec.commonmark.org/0.30/#loose
'& > p': {
marginBottom: theme.spacing(1),
},
},
'& .feature-list': {
padding: 0,
listStyle: 'none',
'& li': {
marginBottom: 6,
display: 'flex',
alignItems: 'center',
gap: 12,
'::before': {
content: `url('/static/branding/pricing/yes-light.svg')`,
width: '18px',
height: '18px',
},
},
},
'& .MuiCode-title': {
padding: theme.spacing(1.5),
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1.5),
borderBottom: `1px solid var(--muidocs-palette-primaryDark-700, ${lightTheme.palette.primaryDark[700]})`,
backgroundColor: `var(--muidocs-palette-primaryDark-900, ${lightTheme.palette.primaryDark[900]})`,
fontFamily: theme.typography.fontFamilyCode,
fontSize: theme.typography.pxToRem(12),
fontWeight: theme.typography.fontWeightBold,
color: `var(--muidocs-palette-grey-200, ${lightTheme.palette.grey[200]})`,
'::before': {
content: `url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.3333 3.99996H8L7.06 3.05996C6.80667 2.80663 6.46667 2.66663 6.11334 2.66663H2.66667C1.93334 2.66663 1.34 3.26663 1.34 3.99996L1.33334 12C1.33334 12.7333 1.93334 13.3333 2.66667 13.3333H13.3333C14.0667 13.3333 14.6667 12.7333 14.6667 12V5.33329C14.6667 4.59996 14.0667 3.99996 13.3333 3.99996ZM12.6667 12H3.33334C2.96667 12 2.66667 11.7 2.66667 11.3333V5.99996C2.66667 5.63329 2.96667 5.33329 3.33334 5.33329H12.6667C13.0333 5.33329 13.3333 5.63329 13.3333 5.99996V11.3333C13.3333 11.7 13.0333 12 12.6667 12Z' fill='%2399CCF3'/%3E%3C/svg%3E%0A");`,
width: '16px',
height: '16px',
},
'& + pre': {
margin: 0,
border: 'none',
borderRadius: 0,
},
},
}),
({ theme }) => ({
[`:where(${theme.vars ? '[data-mui-color-scheme="dark"]' : '.mode-dark'}) &`]: {
color: 'rgb(255, 255, 255)',
'& :not(pre) > code': {
// inline code block
color: `var(--muidocs-palette-text-primary, ${darkTheme.palette.text.primary})`,
borderColor: alpha(darkTheme.palette.primaryDark[600], 0.6),
backgroundColor: `var(--muidocs-palette-grey-900, ${darkTheme.palette.grey[900]})`,
},
'& strong': {
color: `var(--muidocs-palette-grey-200, ${darkTheme.palette.grey[200]})`,
},
'& hr': {
backgroundColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`,
},
'& a': {
color: `var(--muidocs-palette-primary-300, ${darkTheme.palette.primary[300]})`,
},
'& a code': {
color: `var(--muidocs-palette-primary-light, ${darkTheme.palette.primary.light})`,
},
'& h1, & h2, & h3, & h4, & h5': {
color: `var(--muidocs-palette-grey-50, ${darkTheme.palette.grey[50]})`,
'& .anchor-icon, & .comment-link': {
color: `var(--muidocs-palette-primary-300, ${darkTheme.palette.primaryDark[400]})`,
'&:hover': {
color: `var(--muidocs-palette-primary-100, ${darkTheme.palette.primary[100]})`,
borderColor: `var(--muidocs-palette-primary-900, ${darkTheme.palette.primary[900]})`,
backgroundColor: alpha(darkTheme.palette.primary[900], 0.6),
},
},
},
'& p, & ul, & ol': {
color: `var(--muidocs-palette-grey-400, ${darkTheme.palette.grey[400]})`,
},
'& h1 code, & h2 code, & h3 code': {
color: `var(--muidocs-palette-grey-100, ${darkTheme.palette.grey[100]})`,
},
'& table': {
'& .required': {
color: '#a5ffa5',
},
'& .optional': {
color: '#a5b3ff',
},
'& .prop-type, & .slot-defaultClass': {
color: '#ffb6ec',
},
'& .prop-default, & .slot-default': {
borderColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`,
},
},
'& td': {
color: `var(--muidocs-palette-text-secondary, ${darkTheme.palette.text.secondary})`,
borderColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`,
},
'& th': {
color: `var(--muidocs-palette-text-primary, ${darkTheme.palette.text.primary})`,
borderColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`,
},
'& blockquote': {
borderColor: `var(--muidocs-palette-primaryDark-700, ${darkTheme.palette.primaryDark[700]})`,
'&::before': {
color: `var(--muidocs-palette-primaryDark-500, ${darkTheme.palette.primaryDark[500]})`,
},
},
'& .MuiCallout-root': {
borderColor: `var(--muidocs-palette-primaryDark-700, ${darkTheme.palette.primaryDark[700]})`,
'& code': {
backgroundColor: `var(--muidocs-palette-primaryDark-600, ${darkTheme.palette.primaryDark[600]})`,
borderColor: `var(--muidocs-palette-primaryDark-500, ${darkTheme.palette.primaryDark[500]})`,
},
'&.MuiCallout-error': {
color: `var(--muidocs-palette-error-50, ${darkTheme.palette.error[50]})`,
backgroundColor: alpha(darkTheme.palette.error[700], 0.15),
borderColor: alpha(darkTheme.palette.error[400], 0.1),
'& strong': {
color: `var(--muidocs-palette-error-300, ${darkTheme.palette.error[300]})`,
},
'& svg': {
fill: `var(--muidocs-palette-error-500, ${darkTheme.palette.error[500]})`,
},
'& a': {
color: `var(--muidocs-palette-error-200, ${darkTheme.palette.error[200]})`,
},
},
'&.MuiCallout-info': {
color: `var(--muidocs-palette-grey-50, ${darkTheme.palette.grey[50]})`,
backgroundColor: alpha(darkTheme.palette.grey[700], 0.15),
borderColor: alpha(darkTheme.palette.grey[800], 0.5),
'& strong': {
color: `var(--muidocs-palette-primary-200, ${darkTheme.palette.primary[200]})`,
},
'& svg': {
fill: `var(--muidocs-palette-grey-400, ${darkTheme.palette.grey[400]})`,
},
},
'&.MuiCallout-success': {
color: `var(--muidocs-palette-success-50, ${darkTheme.palette.success[50]})`,
backgroundColor: alpha(darkTheme.palette.success[700], 0.12),
borderColor: alpha(lightTheme.palette.success[400], 0.1),
'& strong': {
color: `var(--muidocs-palette-success-200, ${darkTheme.palette.success[200]})`,
},
'& svg': {
fill: `var(--muidocs-palette-success-500, ${darkTheme.palette.success[500]})`,
},
'& a': {
color: `var(--muidocs-palette-success-100, ${darkTheme.palette.success[100]})`,
},
},
'&.MuiCallout-warning': {
color: `var(--muidocs-palette-warning-50, ${darkTheme.palette.warning[50]})`,
backgroundColor: alpha(darkTheme.palette.warning[700], 0.12),
borderColor: alpha(darkTheme.palette.warning[400], 0.1),
'& strong': {
color: `var(--muidocs-palette-warning-200, ${darkTheme.palette.warning[200]})`,
},
'& svg': {
fill: `var(--muidocs-palette-warning-400, ${darkTheme.palette.warning[400]})`,
},
'& a': {
color: `var(--muidocs-palette-warning-100, ${darkTheme.palette.warning[100]})`,
},
},
},
'& kbd.key': {
color: `var(--muidocs-palette-text-primary, ${darkTheme.palette.text.primary})`,
backgroundColor: `var(--muidocs-palette-primaryDark-800, ${darkTheme.palette.primaryDark[800]})`,
border: `1px solid var(--muidocs-palette-primaryDark-600, ${darkTheme.palette.primaryDark[600]})`,
boxShadow: `inset 0 -2px 0 var(--muidocs-palette-primaryDark-700, ${darkTheme.palette.primaryDark[700]})`,
},
'& details': {
borderColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`,
},
'& summary': {
'&:hover': {
backgroundColor: `var(--muidocs-palette-primaryDark-800, ${darkTheme.palette.primaryDark[800]})`,
},
},
'& .feature-list': {
'& li': {
'::before': {
content: `url('/static/branding/pricing/yes-dark.svg')`,
},
},
},
},
}),
);
function handleHeaderClick(event: Event) {
const selection = document.getSelection();
if (selection === null) {
return;
}
if (selection.type === 'Range') {
// Disable the <a> behavior to be able to select text.
event.preventDefault();
}
}
export interface MarkdownElementProps {
className?: string;
renderedMarkdown?: string;
children?: React.ReactNode;
}
export const MarkdownElement = React.forwardRef<HTMLDivElement, MarkdownElementProps>(
function MarkdownElement(props, ref) {
const { className, renderedMarkdown, ...other } = props;
const rootRef = React.useRef<HTMLElement>(null);
const handleRef = useForkRef(rootRef, ref);
React.useEffect(() => {
const elements = rootRef.current!.getElementsByClassName('title-link-to-anchor');
for (let i = 0; i < elements.length; i += 1) {
// More reliable than `-webkit-user-drag` (https://caniuse.com/webkit-user-drag)
elements[i].setAttribute('draggable', 'false');
elements[i].addEventListener('click', handleHeaderClick);
}
}, []);
const more: React.ComponentProps<typeof Root> = {};
if (typeof renderedMarkdown === 'string') {
// workaround for https://github.com/facebook/react/issues/17170
// otherwise we could just set `dangerouslySetInnerHTML={undefined}`
more.dangerouslySetInnerHTML = { __html: renderedMarkdown };
}
return (
<Root className={clsx('markdown-body', className)} {...more} {...other} ref={handleRef} />
);
},
);

View File

@@ -0,0 +1 @@
export * from './MarkdownElement';

View File

@@ -0,0 +1,8 @@
import * as React from 'react';
export interface NProgressBarProps {
children?: React.ReactNode;
}
declare const NProgressBar: React.FunctionComponent<NProgressBarProps>;
export default NProgressBar;

View File

@@ -0,0 +1,98 @@
import PropTypes from 'prop-types';
import NProgress from 'nprogress';
import { NoSsr } from '@mui/base/NoSsr';
import exactProp from '@mui/utils/exactProp';
import GlobalStyles from '@mui/material/GlobalStyles';
import { keyframes } from '@mui/material/styles';
NProgress.configure({
barSelector: '.nprogress-bar',
template: `
<div class="nprogress-bar">
<div></div>
<div></div>
</div>
`,
});
const muiNProgressPulse = keyframes`
30% {
opacity: 0.6;
}
60% {
opacity: 0;
}
to {
opacity: 0.6;
}
`;
/**
* Elegant and ready-to-use wrapper on top of https://github.com/rstacruz/nprogress/.
* The implementation is highly inspired by the YouTube one.
*/
function NProgressBar(props) {
return (
<NoSsr>
{props.children}
<GlobalStyles
styles={(theme) => ({
'#nprogress': {
direction: 'ltr',
pointerEvents: 'none',
position: 'fixed',
top: 0,
left: 0,
right: 0,
height: 2,
zIndex: (theme.vars || theme).zIndex.tooltip,
backgroundColor: (theme.vars || theme).palette.primary[200],
...theme.applyStyles('dark', {
backgroundColor: (theme.vars || theme).palette.primary[700],
}),
'& .nprogress-bar': {
position: 'fixed',
backgroundColor: (theme.vars || theme).palette.primary.main,
top: 0,
left: 0,
right: 0,
height: 2,
},
'& .nprogress-bar > div': {
position: 'absolute',
top: 0,
height: 2,
boxShadow: `${(theme.vars || theme).palette.primary.main} 1px 0 6px 1px`,
borderRadius: '100%',
animation: `${muiNProgressPulse} 2s ease-out 0s infinite`,
},
'& .nprogress-bar > div:first-of-type': {
opacity: 0.6,
width: 20,
right: 0,
clip: 'rect(-6px,22px,14px,10px)',
},
'& .nprogress-bar > div:last-of-type': {
opacity: 0.6,
width: 180,
right: -80,
clip: 'rect(-6px,90px,14px,-6px)',
},
},
})}
/>
</NoSsr>
);
}
NProgressBar.propTypes = {
children: PropTypes.node,
};
if (process.env.NODE_ENV !== 'production') {
NProgressBar.propTypes = exactProp(NProgressBar.propTypes);
}
export default NProgressBar;

View File

@@ -0,0 +1,2 @@
export { default } from './NProgressBar';
export * from './NProgressBar';

View File

@@ -0,0 +1 @@
export { default } from './NProgressBar';

View File

@@ -0,0 +1,20 @@
export interface SectionTitleProps<Hash extends string = string> {
title: string;
hash: Hash;
level?: 'h2' | 'h3' | 'h4';
}
export function SectionTitle(props: SectionTitleProps) {
const { title, hash, level: Level = 'h2' } = props;
return (
<Level id={hash} style={{ flexGrow: 1 }}>
<a aria-labelledby={hash} className="title-link-to-anchor" href={`#${hash}`} tabIndex={-1}>
{title}
<span className="anchor-icon">
<svg>
<use xlinkHref="#anchor-link-icon" />
</svg>
</span>
</a>
</Level>
);
}

View File

@@ -0,0 +1 @@
export * from './SectionTitle';

View File

@@ -0,0 +1,19 @@
import * as React from 'react';
import { ThemeProvider, useTheme } from '@mui/material/styles';
import { brandingDarkTheme, brandingLightTheme } from './brandingTheme';
export interface BrandingProviderProps {
children: React.ReactNode;
/**
* If not `undefined`, the provider is considered nesting and does not render NextNProgressBar & CssBaseline
*/
mode?: 'light' | 'dark';
}
export function BrandingProvider(props: BrandingProviderProps) {
const { children, mode: modeProp } = props;
const upperTheme = useTheme();
const mode = modeProp || upperTheme.palette.mode;
const theme = mode === 'dark' ? brandingDarkTheme : brandingLightTheme;
return <ThemeProvider theme={modeProp ? () => theme : theme}>{children}</ThemeProvider>;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
export * from './brandingTheme';
export * from './BrandingProvider';

View File

@@ -0,0 +1,162 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import deepmerge from '@mui/utils/deepmerge';
import defaultTranslations from '../translations';
const TranslationsContext = React.createContext(defaultTranslations);
interface TranslationsProviderProps {
translations?: Translations;
children: React.ReactNode;
}
function TranslationsProvider({ translations = {}, children }: TranslationsProviderProps) {
const currentTranslations = React.useContext(TranslationsContext);
const mergedTranslations = React.useMemo(
() => deepmerge(currentTranslations, translations),
[currentTranslations, translations],
);
return (
<TranslationsContext.Provider value={mergedTranslations}>
{children}
</TranslationsContext.Provider>
);
}
function getPath(obj: Translations, path: string): string | null {
if (!path || typeof path !== 'string') {
return null;
}
const translation = path
.split('.')
.reduce(
(acc: Translations | string | null, item) =>
(acc && typeof acc === 'object' && acc[item]) || null,
obj,
);
if (typeof translation === 'object') {
return null;
}
return translation;
}
interface UserLanguageContextValue {
userLanguage: string;
setUserLanguage: React.Dispatch<React.SetStateAction<string>>;
}
const UserLanguageContext = React.createContext<UserLanguageContextValue>({
userLanguage: '',
setUserLanguage: () => {},
});
if (process.env.NODE_ENV !== 'production') {
UserLanguageContext.displayName = 'UserLanguage';
}
export interface UserLanguageProviderProps {
children: React.ReactNode;
translations?: Translations;
defaultUserLanguage: string;
}
export function UserLanguageProvider(props: UserLanguageProviderProps) {
const { children, translations, defaultUserLanguage } = props;
const [userLanguage, setUserLanguage] = React.useState(defaultUserLanguage);
const contextValue = React.useMemo(() => {
return { userLanguage, setUserLanguage };
}, [userLanguage]);
return (
<TranslationsProvider translations={translations}>
<UserLanguageContext.Provider value={contextValue}>{children}</UserLanguageContext.Provider>
</TranslationsProvider>
);
}
UserLanguageProvider.propTypes = {
children: PropTypes.node.isRequired,
defaultUserLanguage: PropTypes.string,
};
export function useUserLanguage() {
return React.useContext(UserLanguageContext).userLanguage;
}
export function useSetUserLanguage() {
return React.useContext(UserLanguageContext).setUserLanguage;
}
const warnedOnce: Record<string, boolean> = {};
// TODO, migrate to use warnOnce() helper
const warn = (userLanguage: string, key: string, ignoreWarning: boolean) => {
const fullKey = `${userLanguage}:${key}`;
// No warnings in CI env
if (!ignoreWarning && !warnedOnce[fullKey] && typeof window !== 'undefined') {
console.warn(`Missing translation for ${fullKey}`);
warnedOnce[fullKey] = true;
}
};
export interface TranslateOptions {
ignoreWarning?: boolean;
}
export type Translate = (key: string, options?: TranslateOptions) => any;
export function useTranslate(): Translate {
const userLanguage = useUserLanguage();
const translations = React.useContext(TranslationsContext);
return React.useMemo(
() =>
function translate(key: string, options: TranslateOptions = {}) {
const { ignoreWarning = false } = options;
const wordings = translations[userLanguage];
if (!wordings) {
console.error(`Missing language: ${userLanguage}.`);
return '…';
}
const translation = getPath(wordings, key);
if (!translation) {
warn(userLanguage, key, ignoreWarning);
const enTranslation = getPath(translations.en, key);
return enTranslation ?? null;
}
return translation;
},
[userLanguage, translations],
);
}
export type Translations = { [key in string]?: string | Translations };
export interface RequireContext {
(req: string): string;
keys: () => string[];
}
export function mapTranslations(req: RequireContext): Translations {
const result: Translations = {};
req.keys().forEach((filename) => {
const match = filename.match(/-([a-z]{2}).json$/);
if (match) {
result[match[1]] = req(filename);
} else {
result.en = req(filename);
}
});
return result;
}

View File

@@ -0,0 +1 @@
export * from './i18n';

View File

@@ -0,0 +1,15 @@
import { createSvgIcon } from '@mui/material/utils';
export default createSvgIcon(
<g fill="currentColor" fillRule="nonzero">
<path
d="M5.84 3c-.52 0-1 .25-1.3.67l-1.4 2.05c-.06.09-.1.19-.14.28h8V3H5.84zM20.86 5.72l-1.4-2.05c-.3-.42-.81-.67-1.33-.67H13v3h8c-.05-.1-.08-.2-.14-.28z"
fillOpacity=".79"
/>
<path
d="M20.98 7H3.02L3 7.11V19.4c0 .89.71 1.61 1.58 1.61h14.84A1.6 1.6 0 0021 19.4V7.1L20.98 7zm-6.87 5.36H9.89a1.6 1.6 0 01-1.58-1.61c0-.89.7-1.6 1.58-1.6h4.22c.87 0 1.58.71 1.58 1.6 0 .89-.7 1.6-1.58 1.6z"
fillOpacity=".87"
/>
</g>,
'BundleSize',
);

View File

@@ -0,0 +1,12 @@
import { createSvgIcon } from '@mui/material/utils';
export default createSvgIcon(
<g fillRule="nonzero" fill="none">
<path d="M8 24a4 4 0 004-4v-4H8a4 4 0 000 8z" fill="#0ACF83" />
<path d="M4 12a4 4 0 014-4h4v8H8a4 4 0 01-4-4z" fill="#A259FF" />
<path d="M4 4a4 4 0 014-4h4v8H8a4 4 0 01-4-4z" fill="#F24E1E" />
<path d="M12 0h4a4 4 0 010 8h-4V0z" fill="#FF7262" />
<path d="M20 12a4 4 0 11-8 0 4 4 0 018 0z" fill="#1ABCFE" />
</g>,
'Figma',
);

View File

@@ -0,0 +1,13 @@
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
function FileDownload(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
</SvgIcon>
);
}
FileDownload.muiName = 'SvgIcon';
export default FileDownload;

View File

@@ -0,0 +1,13 @@
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
function JavaScript(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="M3,3H21V21H3V3M7.73,18.04C8.13,18.89 8.92,19.59 10.27,19.59C11.77,19.59 12.8,18.79 12.8,17.04V11.26H11.1V17C11.1,17.86 10.75,18.08 10.2,18.08C9.62,18.08 9.38,17.68 9.11,17.21L7.73,18.04M13.71,17.86C14.21,18.84 15.22,19.59 16.8,19.59C18.4,19.59 19.6,18.76 19.6,17.23C19.6,15.82 18.79,15.19 17.35,14.57L16.93,14.39C16.2,14.08 15.89,13.87 15.89,13.37C15.89,12.96 16.2,12.64 16.7,12.64C17.18,12.64 17.5,12.85 17.79,13.37L19.1,12.5C18.55,11.54 17.77,11.17 16.7,11.17C15.19,11.17 14.22,12.13 14.22,13.4C14.22,14.78 15.03,15.43 16.25,15.95L16.67,16.13C17.45,16.47 17.91,16.68 17.91,17.26C17.91,17.74 17.46,18.09 16.76,18.09C15.93,18.09 15.45,17.66 15.09,17.06L13.71,17.86Z" />
</SvgIcon>
);
}
JavaScript.muiName = 'SvgIcon';
export default JavaScript;

View File

@@ -0,0 +1,22 @@
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
export default function MarkdownIcon(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<title>Markdown</title>
<path
d="M22.269 19.385H1.731a1.73 1.73 0 0 1-1.73-1.73V6.345a1.73 1.73 0 0 1 1.73-1.73h20.538a1.73 1.73 0 0 1 1.73 1.73v11.308a1.73 1.73 0 0 1-1.73 1.731zm-16.5-3.462v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.461v7.847zM21.231 12h-2.308V8.077h-2.307V12h-2.308l3.461 4.039z"
fill="currentColor"
/>
</svg>
</SvgIcon>
);
}

View File

@@ -0,0 +1,10 @@
import { createSvgIcon } from '@mui/material/utils';
export default createSvgIcon(
<g fill="none" fillRule="evenodd">
<circle fill="#737373" cx="12" cy="12" r="12" />
<path fill="#BDBDBD" d="M4 4h16v16H4z" />
<path fill="#FFF" d="M12 20l8-16H4z" />
</g>,
'MaterialDesign',
);

View File

@@ -0,0 +1,15 @@
import { createSvgIcon } from '@mui/material/utils';
export default createSvgIcon(
<g fillRule="nonzero" fill="none">
<path fill="#FDB300" d="M5.24 2.7L12 2l6.76.7L24 9.48 12 23 0 9.49z" />
<path fill="#EA6C00" d="M4.85 9l7.13 14L0 9zM19.1 9l-7.12 14L23.95 9z" />
<path fill="#FDAD00" d="M4.85 9H19.1l-7.12 14z" />
<g>
<path fill="#FDD231" d="M11.98 2l-6.75.65-.38 6.34zM11.98 2l6.75.65.37 6.34z" />
<path fill="#FDAD00" d="M23.95 9l-5.22-6.35.37 6.34zM0 9l5.23-6.35-.38 6.34z" />
<path fill="#FEEEB7" d="M11.98 2L4.85 9H19.1z" />
</g>
</g>,
'Sketch',
);

View File

@@ -0,0 +1,13 @@
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
function TypeScript(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="M3,3H21V21H3V3M13.71,17.86C14.21,18.84 15.22,19.59 16.8,19.59C18.4,19.59 19.6,18.76 19.6,17.23C19.6,15.82 18.79,15.19 17.35,14.57L16.93,14.39C16.2,14.08 15.89,13.87 15.89,13.37C15.89,12.96 16.2,12.64 16.7,12.64C17.18,12.64 17.5,12.85 17.79,13.37L19.1,12.5C18.55,11.54 17.77,11.17 16.7,11.17C15.19,11.17 14.22,12.13 14.22,13.4C14.22,14.78 15.03,15.43 16.25,15.95L16.67,16.13C17.45,16.47 17.91,16.68 17.91,17.26C17.91,17.74 17.46,18.09 16.76,18.09C15.93,18.09 15.45,17.66 15.09,17.06L13.71,17.86M13,11.25H8V12.75H9.5V20H11.25V12.75H13V11.25Z" />
</SvgIcon>
);
}
TypeScript.muiName = 'SvgIcon';
export default TypeScript;

View File

@@ -0,0 +1,14 @@
import { createSvgIcon } from '@mui/material/utils';
export default createSvgIcon(
<g fillRule="nonzero" fill="none">
<path
d="M6.92 6.1l2.33 7.99 2.32-8h6.3v.8l-2.37 4.14c.83.27 1.46.76 1.89 1.47.43.71.64 1.55.64 2.51 0 1.2-.31 2.2-.94 3a2.93 2.93 0 01-2.42 1.22 2.9 2.9 0 01-1.96-.72 4.25 4.25 0 01-1.23-1.96l1.31-.55c.2.5.45.9.76 1.18.32.28.69.43 1.12.43.44 0 .82-.26 1.13-.76.31-.51.47-1.12.47-1.84 0-.79-.17-1.4-.5-1.83-.38-.5-.99-.76-1.81-.76h-.64v-.78l2.24-3.92h-2.7l-.16.26-3.3 11.25h-.15l-2.4-8.14-2.41 8.14h-.16L.43 6.1H2.1l2.33 7.99L6 8.71 5.24 6.1h1.68z"
fill="#005A9C"
/>
<g fill="currentColor">
<path d="M23.52 6.25l.28 1.62-.98 1.8s-.38-.76-1.01-1.19c-.53-.35-.87-.43-1.41-.33-.7.14-1.48.93-1.82 1.9-.41 1.18-.42 1.74-.43 2.26a4.9 4.9 0 00.11 1.33s-.6-1.06-.59-2.61c0-1.1.19-2.11.72-3.1.47-.87 1.17-1.4 1.8-1.45.63-.07 1.14.23 1.53.55.42.33.83 1.07.83 1.07l.97-1.85zM23.64 15.4s-.43.75-.7 1.04c-.27.28-.76.79-1.36 1.04-.6.25-.91.3-1.5.25a3.03 3.03 0 01-1.34-.52 5.08 5.08 0 01-1.67-2.04s.24.75.39 1.07c.09.18.36.74.74 1.23a3.5 3.5 0 002.1 1.42c1.04.18 1.76-.27 1.94-.38a5.32 5.32 0 001.4-1.43c.1-.14.25-.43.25-.43l-.25-1.25z" />
</g>
</g>,
'W3C',
);

View File

@@ -0,0 +1,6 @@
import type { Translations } from '../i18n';
import en from './translations.json';
export default {
en,
} as Record<string, Translations>;

View File

@@ -0,0 +1,223 @@
{
"adblock": "If you don't mind tech-related ads (no tracking or remarketing), and want to keep us running, please whitelist us in your blocker.",
"adPublisher": "ad by {{publisher}}",
"aiCustomizeDemo": "Want to customize this?",
"aiChatFailed": "Failed to open MUI Chat",
"api-docs": {
"componentName": "Component name",
"componentsApi": "Components API",
"themeDefaultProps": "Theme default props",
"themeDefaultPropsDescription": "You can use <code>{{muiName}}</code> to change the default props of this component <a href={{defaultPropsLink}}>with the theme</a>.",
"classes": "CSS classes",
"classesDescription": "These class names are useful for styling with CSS. They are applied to the component's slots when specific states are triggered.",
"className": "Class name",
"cssDescription": "The following class names are useful for styling with CSS (the <a href=\"/material-ui/customization/how-to-customize/#state-classes\">state classes</a> are marked). <br /> To learn more, visit the <a href=\"/material-ui/customization/theme-components/\">component customization</a> page.",
"css": "CSS",
"cssComponent": "As a CSS utility, the {{name}} component also supports all <a href=\"/system/properties/\"><code>system</code></a> properties. You can use them as props directly on the component.",
"default": "Default",
"defaultComponent": "Default component",
"defaultValue": "Default value",
"defaultHTMLTag": "Default HTML tag",
"defaultDeprecationMessage": "This API is deprecated.",
"demos": "Demos",
"deprecated": "Deprecated",
"description": "Description",
"globalClass": "Global class",
"defaultClass": "Default class",
"hookName": "Hook name",
"hooksApi": "Hooks API",
"hooksNoParameters": "This hook does not accept any input parameters.",
"hooksPageDescription": "API reference docs for the {{name}} hook. Learn about the input parameters and other APIs of this exported module.",
"import": "Import",
"importDifference": "Learn about the difference by <a href=\"/material-ui/guides/minimizing-bundle-size/\">reading this guide on minimizing bundle size</a>.",
"inheritance": "Inheritance",
"inheritanceDescription": "While not explicitly documented above, the props of the <a href=\"{{pathname}}\">{{component}}</a> component{{suffix}} are also available in {{name}}. You can take advantage of this to <a href=\"/material-ui/guides/api/#spread\">target nested components</a>.",
"inheritanceSuffixTransition": " from react-transition-group",
"name": "Name",
"nativeElement": "native",
"overrideStyles": "You can override the style of the component using one of these customization options:\n",
"overrideStylesStyledComponent": "<ul>\n<li>With a <a href=\"/material-ui/integrations/interoperability/#global-css\">global class name</a>.</li>\n<li>With a rule name as part of the component's <a href=\"{{styleOverridesLink}}\"><code>styleOverrides</code> property</a> in a custom theme.</li>\n</ul>",
"pageDescription": "API reference docs for the React {{name}} component. Learn about the props, CSS, and other APIs of this exported module.",
"props": "Props",
"properties": "Properties",
"parameters": "Parameters",
"requires-ref": "This <a href=\"/material-ui/guides/composition/#caveat-with-refs\">needs to be able to hold a ref</a>.",
"returns": "Returns: ",
"returnValue": "Return value",
"refNotHeld": "The component cannot hold a ref.",
"refRootElement": "The <code>ref</code> is forwarded to the root element.",
"ruleName": "Rule name",
"seeSourceCode": "If you did not find the information in this page, consider having a look at the <a href=\"{{href}}\">implementation of the component</a> for more detail.",
"signature": "Signature",
"slots": "Slots",
"spreadHint": "Props of the {{spreadHintElement}} component are also available.",
"state": "STATE",
"styleOverrides": "The name <code>{{componentStyles.name}}</code> can be used when providing <a href={{defaultPropsLink}}>default props</a> or <a href={{styleOverridesLink}}>style overrides</a> in the theme.",
"slotDescription": "To learn how to customize the slot, check out the <a href={{slotGuideLink}}>Overriding component structure</a> guide.",
"slotName": "Slot name",
"source-code": "Source code",
"type": "Type",
"required": "Required",
"optional": "Optional",
"additional-info": {
"cssApi": "See <a href='#classes'>CSS classes API</a> below for more details.",
"sx": "See the <a href='/system/getting-started/the-sx-prop/'>`sx` page</a> for more details.",
"slotsApi": "See <a href='#slots'>Slots API</a> below for more details.",
"joy-size": "To learn how to add custom sizes to the component, check out <a href='/joy-ui/customization/themed-components/#extend-sizes'>Themed components—Extend sizes</a>.",
"joy-color": "To learn how to add your own colors, check out <a href='/joy-ui/customization/themed-components/#extend-colors'>Themed components—Extend colors</a>.",
"joy-variant": "To learn how to add your own variants, check out <a href='/joy-ui/customization/themed-components/#extend-variants'>Themed components—Extend variants</a>."
}
},
"marketingPageDescr": "A responsive marketing page layout with sections for product features, testimonials, pricing, and FAQs.",
"marketingPageTitle": "Marketing page",
"searchButton": "Search…",
"algoliaSearch": "What are you looking for?",
"appFrame": {
"changeLanguage": "Change language",
"github": "GitHub repository",
"helpToTranslate": "Help to translate",
"openDrawer": "Open main navigation",
"skipToContent": "Skip to content",
"toggleSettings": "Toggle settings drawer"
},
"backToTop": "Scroll back to top",
"backToOpenRoles": "Back to open roles",
"blogDescr": "A sleek, modern blog homepage layout with Markdown support via markdown-to-jsx.",
"blogTitle": "Blog",
"bundleSize": "Bundle size",
"bundleSizeTooltip": "Scroll down to 'Exports Analysis' for a more detailed report.",
"cancel": "Cancel",
"cdn": "or use a CDN.",
"checkoutDescr": "A sophisticated checkout flow with fully customizable steps.",
"checkoutTitle": "Checkout",
"clickToCopy": "Click to copy",
"close": "Close",
"codesandbox": "Edit in CodeSandbox",
"copied": "Copied",
"copiedSource": "The source code has been copied to your clipboard.",
"copiedSourceLink": "Link to the source code has been copied to your clipboard.",
"copySource": "Copy the source",
"copySourceLinkJS": "Copy link to JavaScript source",
"copySourceLinkTS": "Copy link to TypeScript source",
"dashboardDescr": "A complex data visualization dashboard featuring the MUI X Data Grid and Charts.",
"dashboardTitle": "Dashboard",
"decreaseSpacing": "decrease spacing",
"demoToolbarLabel": "demo source",
"demoStylingSelectSystem": "MUI System",
"demoStylingSelectTailwind": "Tailwind CSS",
"demoStylingSelectCSS": "Plain CSS",
"diamondSponsors": "Diamond sponsors",
"becomeADiamondSponsor": "Become a Diamond sponsor",
"diamondSponsorVacancies": "One spot left!",
"editorHint": "Press <kbd>Enter</kbd> to start editing",
"editPage": "Edit this page",
"emojiLove": "Love",
"emojiWarning": "Warning",
"expandAll": "Expand all",
"feedbackCommentLabel": "Comment",
"feedbackFailed": "Couldn't submit feedback. Please try again later.",
"feedbackMessage": "Was this page helpful?",
"feedbackMessageDown": "How can we improve this page? (optional)",
"feedbackMessageUp": "What did you like about this page? (optional)",
"feedbackSectionSpecific": "How can we improve the <strong>{{sectionName}}</strong> section? (optional)",
"feedbackMessageToGitHub": {
"usecases": "If something is broken or if you need a reply to a problem you've encountered, please",
"reasonWhy": "Otherwise, the team won't be able to answer back or ask for more information.",
"callToAction": {
"link": "open an issue instead."
}
},
"feedbackNo": "No",
"feedbackSubmitted": "Feedback submitted",
"feedbackYes": "Yes",
"footerCompany": "Company",
"goToHome": "go to homepage",
"getProfessionalSupport": "Get Professional Support",
"getStarted": "Get Started",
"githubLabel": "Feedback",
"headTitle": "MUI: A popular React UI framework",
"hideFullSource": "Collapse code",
"hideSource": "Hide code",
"homeQuickWord": "A quick word from our sponsors:",
"increaseSpacing": "increase spacing",
"initialFocusLabel": "A generic container that is programmatically focused to test keyboard navigation of our components.",
"installation": "Installation",
"installButton": "Read installation docs",
"installDescr": "Install MUI's source files via npm. We take care of injecting the CSS needed.",
"joinThese": "Join these and other great organizations!",
"JS": "JavaScript",
"letUsKnow": "Let us know!",
"likeMui": "Help us keep running",
"loadFont": "Load the default Roboto font.",
"mainNavigation": "documentation",
"newest": "Newest",
"openDrawer": "Open documentation navigation",
"or": "or",
"pageTOC": "Page table of contents",
"praise": "Praise for MUI",
"praiseDescr": "Here's what some of our users are saying.",
"pricingDescr": "Quickly build an effective pricing table for your potential customers.",
"pricingTitle": "Pricing",
"resetDemo": "Reset demo",
"resetDensity": "Reset density",
"resetFocus": "Reset focus to test keyboard navigation",
"searchIcons": {
"learnMore": "Learn more about the import"
},
"seeMore": "See more",
"settings": {
"color": "Color",
"dark": "Dark",
"direction": "Direction",
"editDocsColors": "Edit documentation colors",
"light": "Light",
"ltr": "Left to right",
"mode": "Mode",
"rtl": "Right to left",
"settings": "Settings",
"system": "System",
"language": "Language"
},
"showFullSource": "Expand code",
"showJSSource": "Show JavaScript source",
"showSource": "Show code",
"showTSSource": "Show TypeScript source",
"signInDescr": "A clean and efficient sign-in page, ready for your favorite auth provider.",
"signInSideDescr": "A responsive, two-column sign-in page for adding content alongside the form.",
"signInSideTitle": "Sign-in side",
"signInTitle": "Sign-in",
"signUpDescr": "A clean and efficient sign-up page, perfect for pairing with a sign-in template.",
"signUpTitle": "Sign-up",
"crudDashboardTitle": "CRUD dashboard",
"crudDashboardDescr": "Dashboard with CRUD pages and mobile-friendly layout with highly customizable sidebar.",
"sourceCode": "Source code",
"spacingUnit": "Spacing unit",
"stackblitz": "Edit in StackBlitz",
"stars": "GitHub stars",
"stickyFooterDescr": "Attach a footer to the bottom of the viewport when page content is short.",
"stickyFooterTitle": "Sticky footer",
"strapline": "MUI provides a simple, customizable, and accessible library of React components. Follow your own design system, or start with Material Design.",
"submit": "Submit",
"tableOfContents": "Contents",
"thanks": "Thank you!",
"themes": "Premium themes",
"themesButton": "Browse themes",
"themesDescr": "Take your project to the next level with premium themes from our store all built on MUI.",
"toggleNotifications": "Toggle notifications panel",
"toggleRTL": "Toggle right-to-left/left-to-right",
"traffic": "Traffic",
"TS": "TypeScript",
"v5IsOut": "🎉 v5 release candidate is out! Head to the",
"v5docsLink": "v5 documentation",
"v5startAdoption": "to get started.",
"unreadNotifications": "unread notifications",
"usage": "Usage",
"usageButton": "Explore the docs",
"usageDescr": "MUI components work without any additional setup, and don't pollute the global scope.",
"useDarkTheme": "Use dark theme",
"useHighDensity": "Apply higher density via props",
"usingMui": "Are you using MUI?",
"viewGitHub": "View the source on GitHub",
"visit": "Visit the website",
"whosUsing": "Who's using MUI?"
}

View File

@@ -0,0 +1,7 @@
export default function loadScript(src: string, position: HTMLElement) {
const script = document.createElement('script');
script.setAttribute('async', '');
script.src = src;
position.appendChild(script);
return script;
}