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,180 @@
import * as React from 'react';
import { useRouter } from 'next/router';
import { deepmerge } from '@mui/utils';
import { ThemeProvider, createTheme, PaletteColorOptions } from '@mui/material/styles';
import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/material/utils';
import { colorChannel, getContrastRatio, lighten, darken } from '@mui/system/colorManipulator';
import CssBaseline from '@mui/material/CssBaseline';
import { getCookie, pathnameToLanguage } from 'docs/src/modules/utils/helpers';
// @ts-ignore to bypass type checking in MUI X repo
import { NextNProgressBar } from 'docs/src/modules/components/AppFrame';
import { getDesignTokens, getThemedComponents } from '@mui/docs/branding';
import SkipLink from 'docs/src/modules/components/SkipLink';
// @ts-ignore to bypass type checking in MUI X repo
import MarkdownLinks from 'docs/src/modules/components/MarkdownLinks';
declare module '@mui/material/styles' {
interface PaletteOptions {
primaryDark?: PaletteColorOptions;
}
}
const { palette: lightPalette, typography, ...designTokens } = getDesignTokens('light');
const { palette: darkPalette } = getDesignTokens('dark');
const themeOptions = {
colorSchemes: {
light: {
palette: lightPalette,
},
dark: {
palette: darkPalette,
},
},
...designTokens,
typography: deepmerge(typography, {
h1: {
':where([data-mui-color-scheme="dark"]) &': {
color: 'var(--muidocs-palette-common-white)',
},
},
h2: {
':where([data-mui-color-scheme="dark"]) &': {
color: 'var(--muidocs-palette-grey-100)',
},
},
h5: {
':where([data-mui-color-scheme="dark"]) &': {
color: 'var(--muidocs-palette-primary-300)',
},
},
}),
...getThemedComponents(),
};
export function setDocsColors(primary: Record<string, string>, secondary: Record<string, string>) {
function injectPalette(prefix: string, palette: string, color: string) {
// simplified logic of `createPalette` to avoid `useTheme`.
const light = lighten(color, 0.2);
const dark = darken(color, 0.3);
const contrastText = getContrastRatio(color, '#fff') >= 3 ? '#fff' : 'rgba(0, 0, 0, 0.87)';
document.documentElement.style.setProperty(`--${prefix}-palette-${palette}-main`, color);
document.documentElement.style.setProperty(
`--${prefix}-palette-${palette}-mainChannel`,
colorChannel(color),
);
document.documentElement.style.setProperty(`--${prefix}-palette-${palette}-light`, light);
document.documentElement.style.setProperty(
`--${prefix}-palette-${palette}-lightChannel`,
colorChannel(light),
);
document.documentElement.style.setProperty(`--${prefix}-palette-${palette}-dark`, dark);
document.documentElement.style.setProperty(
`--${prefix}-palette-${palette}-darkChannel`,
colorChannel(dark),
);
document.documentElement.style.setProperty(
`--${prefix}-palette-${palette}-contrastText`,
contrastText,
);
document.documentElement.style.setProperty(
`--${prefix}-palette-${palette}-contrastTextChannel`,
colorChannel(contrastText),
);
}
if (typeof document !== 'undefined') {
injectPalette('muidocs', 'primary', primary.main);
injectPalette('muidocs', 'secondary', secondary.main);
['50', '100', '200', '300', '400', '500', '600', '700', '800', '900'].forEach((key) => {
document.documentElement.style.setProperty(`--muidocs-palette-primary-${key}`, primary[key]);
document.documentElement.style.setProperty(
`--muidocs-palette-secondary-${key}`,
secondary[key],
);
});
injectPalette('mui', 'primary', primary.main);
injectPalette('mui', 'secondary', secondary.main);
}
}
export function resetDocsColor() {
if (typeof document !== 'undefined') {
document.documentElement.style.removeProperty('--muidocs-palette-primary-main');
document.documentElement.style.removeProperty('--muidocs-palette-secondary-main');
document.documentElement.style.removeProperty('--mui-palette-primary-main');
document.documentElement.style.removeProperty('--mui-palette-secondary-main');
['50', '100', '200', '300', '400', '500', '600', '700', '800', '900'].forEach((key) => {
document.documentElement.style.removeProperty(`--muidocs-palette-primary-${key}`);
document.documentElement.style.removeProperty(`--muidocs-palette-secondary-${key}`);
});
}
}
export function setDocsSpacing(value: number) {
if (typeof document !== 'undefined') {
document.documentElement.style.setProperty('--muidocs-spacing', `${value}px`);
document.documentElement.style.setProperty('--mui-spacing', `${value}px`);
}
}
export function resetDocsSpacing() {
if (typeof document !== 'undefined') {
document.documentElement.style.removeProperty('--muidocs-spacing');
document.documentElement.style.removeProperty('--mui-spacing');
}
}
export default function BrandingCssVarsProvider(props: {
children: React.ReactNode;
direction?: 'ltr' | 'rtl';
}) {
const { direction = 'ltr', children } = props;
const { asPath } = useRouter();
const { canonicalAs } = pathnameToLanguage(asPath);
const theme = React.useMemo(() => {
return createTheme({
cssVariables: {
cssVarPrefix: 'muidocs',
colorSchemeSelector: 'data-mui-color-scheme',
},
direction,
...themeOptions,
});
}, [direction]);
useEnhancedEffect(() => {
const nextPaletteColors = JSON.parse(getCookie('paletteColors') || 'null');
if (nextPaletteColors) {
setDocsColors(nextPaletteColors.primary, nextPaletteColors.secondary);
}
}, []);
useEnhancedEffect(() => {
// This is required to ensure that the layer order is declared first in the head
// because when the direction is RTL on the client, emotion reinserts the RTL styles back to the top of the insertion point.
if (direction === 'rtl') {
const head = document.querySelector('head');
if (head) {
const style = document.createElement('style');
style.textContent =
'@layer theme, docsearch, mui, mui.global, mui.default, mui.theme, mui.custom, mui.sx, utilities;';
head.prepend(style);
}
}
}, [direction]);
return (
<ThemeProvider
theme={theme}
disableTransitionOnChange
// TODO: remove `forceThemeRerender` once custom theme on some demos rely on CSS variables instead of `theme.palette.mode`
forceThemeRerender={canonicalAs.startsWith('/x/') || canonicalAs.startsWith('/toolpad/')}
>
<NextNProgressBar />
<CssBaseline />
<SkipLink />
<MarkdownLinks />
{children}
</ThemeProvider>
);
}

71
docs/src/MuiPage.ts Normal file
View File

@@ -0,0 +1,71 @@
import * as React from 'react';
import standardNavIcons from './modules/components/AppNavIcons';
export type MuiPageIcon = keyof typeof standardNavIcons | React.ComponentType;
export interface MuiPage {
pathname: string;
query?: object;
children?: MuiPage[];
disableDrawer?: boolean;
icon?: MuiPageIcon;
/**
* Indicates if the pages are regarding some legacy API.
*/
legacy?: boolean;
/**
* Indicates if the pages are only available in some plan.
* @default 'community'
*/
plan?: 'community' | 'pro' | 'premium';
/**
* In case the children have pathnames out of pathname value, use this field to scope other pathnames.
* Pathname can be partial, for example '/components/' will cover '/components/button/' and '/components/link/'.
* @deprecated Dead code, to remove.
*/
scopePathnames?: string[];
/**
* Pages are considered to be ordered depth-first.
* If a page should be excluded from this order, set `order: false`.
* You want to set `inSideNav: false` if you don't want the page to appear in an ordered list e.g. for previous/next page navigation.
*/
inSideNav?: boolean;
/**
* Props spread to the Link component.
*/
linkProps?: Record<string, unknown>;
/**
* Subheader to display before navigation links.
*/
subheader?: string;
/**
* Overrides the default page title.
*/
title?: string;
/**
* Indicates if the feature has been recently released.
* @default false
*/
newFeature?: boolean;
/**
* Indicates if the feature is planned for development.
* @default false
*/
planned?: boolean;
/**
* Indicates if the component/hook is not stable yet.
*/
unstable?: boolean;
/**
* Indicates the item is in beta release.
*/
beta?: boolean;
/**
* Indicates if the pages are regarding some deprecated API.
*/
deprecated?: boolean;
}
export interface OrderedMuiPage extends MuiPage {
ordered?: true;
}

View File

@@ -0,0 +1,101 @@
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import SearchOffRoundedIcon from '@mui/icons-material/SearchOffRounded';
function NotFoundIllustration() {
return (
<Box
sx={(theme) => ({
mx: 'auto',
mb: 4,
height: { xs: 200, sm: 150 },
width: { xs: 100, sm: 200 },
display: 'flex',
flexDirection: { xs: 'column-reverse', sm: 'column' },
borderRadius: 1,
border: `1px solid ${theme.palette.grey[200]}`,
overflow: 'clip',
boxShadow: `0px 2px 8px -2px ${alpha(
theme.palette.primary[300],
0.3,
)}, 0px 6px 12px -2px ${alpha(theme.palette.primary[100], 0.2)}`,
...theme.applyDarkStyles({
borderColor: theme.palette.primaryDark[700],
boxShadow: `0px 2px 8px -2px ${alpha(
theme.palette.common.black,
0.3,
)}, 0px 6px 12px -2px ${alpha(theme.palette.common.black, 0.2)}`,
}),
})}
>
<Box
sx={{
p: 1.5,
display: { xs: 'none', sm: 'flex' },
gap: '6px',
borderBottom: '1px solid',
borderColor: 'divider',
bgcolor: 'background.paper',
}}
>
<Box
sx={{ width: 10, height: 10, borderRadius: 2, bgcolor: 'error.500', opacity: '80%' }}
/>
<Box
sx={{ width: 10, height: 10, borderRadius: 2, bgcolor: 'warning.500', opacity: '80%' }}
/>
<Box
sx={{ width: 10, height: 10, borderRadius: 2, bgcolor: 'success.500', opacity: '80%' }}
/>
</Box>
<Box
sx={{
pt: 1,
pb: '5px',
display: { xs: 'flex', sm: 'none' },
justifyContent: 'center',
borderTop: '1px solid',
borderColor: 'divider',
bgcolor: 'background.paper',
}}
>
<Box sx={{ height: 3, width: '40%', bgcolor: 'rgba(0,0,0,0.3)', borderRadius: 2 }} />
</Box>
<Box sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<SearchOffRoundedIcon sx={{ fontSize: 50, color: 'primary.500', opacity: '40%' }} />
</Box>
</Box>
);
}
export default function NotFoundHero() {
return (
<Section
bg="gradient"
sx={{
display: 'flex',
alignItems: 'center',
'& .MuiContainer-root': {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
},
}}
>
<NotFoundIllustration />
<SectionHeadline
alwaysCenter
title={
<Typography component="h1" variant="h4" sx={{ fontWeight: 'semiBold' }}>
Page not found
</Typography>
}
description="Apologies, but the page you were looking for wasn't found. Try reaching for the search button on the nav bar above to look for another one."
/>
</Section>
);
}

View File

@@ -0,0 +1,61 @@
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import { Link } from '@mui/docs/Link';
import GradientText from 'docs/src/components/typography/GradientText';
import ROUTES from 'docs/src/route';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
export default function AboutEnd() {
return (
<Section bg="gradient" sx={{ p: { sm: 8 } }}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'relative',
}}
>
<SectionHeadline
alwaysCenter
overline="Join us"
title={
<Typography variant="h2">
<GradientText>Build the next generation</GradientText>
<br /> of tools for UI development
</Typography>
}
description="We give developers and designers the tools to bring stunning user interfaces to life with unrivaled speed and ease."
/>
<Button
component={Link}
noLinkStyle
href={ROUTES.careers}
endIcon={<KeyboardArrowRightRounded fontSize="small" />}
variant="contained"
sx={{ width: { xs: '100%', sm: 'fit-content' } }}
>
View careers
</Button>
</Box>
<Box
component="img"
src="/static/branding/about/illustrations/team-globe-distribution-light.png"
alt="A map illustration with pins loosely positioned where team members from MUI are located."
loading="lazy"
sx={(theme) => ({
mt: -20,
display: { xs: 'none', sm: 'block' },
width: '100%',
aspectRatio: '231/145',
...theme.applyDarkStyles({
content: 'url(/static/branding/about/illustrations/team-globe-distribution-dark.png)',
}),
})}
/>
</Section>
);
}

View File

@@ -0,0 +1,166 @@
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { styled, keyframes } from '@mui/material/styles';
import Section from 'docs/src/layouts/Section';
import GradientText from 'docs/src/components/typography/GradientText';
import TeamStatistics from 'docs/src/components/about/TeamStatistics';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
const teamPhotos = [
{
img: '/static/branding/about/group-photo/teide-group.jpg',
title:
'A group photo of the MUI crew posing near the base of Mount Teide at the start of the hike.',
},
{
img: '/static/branding/about/group-photo/skiers.jpg',
title: 'MUI team members standing lined-up in the snow with their skigear.',
},
{
img: '/static/branding/about/group-photo/group-photo.jpg',
title: 'Photo of the MUI team in front of the pool at our accommodations in Tenerife',
},
{
img: '/static/branding/about/group-photo/team-dinner.png',
title: 'Members of the MUI team sitting around a large wooden dining table.',
},
{
img: '/static/branding/about/group-photo/working-table-tenerife.png',
title: 'The Toolpad team working together on a heads-down moment in Tenerife.',
},
{
img: '/static/branding/about/group-photo/scuba-gear.png',
title:
'MUI team members and their diving instructors pose in scuba gear before a scuba diving lesson.',
},
{
img: '/static/branding/about/group-photo/outdoor-focus-group.png',
title:
'An impromptu focus group gathered next to the pool to discuss cross-team marketing strategies.',
},
{
img: '/static/branding/about/group-photo/working-table-portugal.png',
title: 'MUI team members working together on a heads-down moment in Portugal.',
},
{
img: '/static/branding/about/group-photo/snow-tea.png',
title: 'The team shares a cup of tea up in the mountains of Chamonix, France.',
},
{
img: '/static/branding/about/group-photo/portugal-sight-seeing.png',
title: 'MUI team selfie while sightseeing in Lisbon, Portugal.',
},
];
const ImageContainer = styled('div')(() => ({
display: 'flex',
gap: 16,
justifyContent: 'center',
}));
const Image = styled('img')(({ theme }) => ({
width: 400,
height: 300,
boxSizing: 'content-box',
objectFit: 'cover',
borderRadius: theme.shape.borderRadius,
border: '1px solid',
borderColor: (theme.vars || theme).palette.divider,
boxShadow: `0px 2px 8px ${(theme.vars || theme).palette.grey[200]}`,
transition: 'all 100ms ease',
...theme.applyDarkStyles({
borderColor: (theme.vars || theme).palette.primaryDark[600],
boxShadow: `0px 2px 8px ${(theme.vars || theme).palette.common.black}`,
}),
}));
const scroll = keyframes`
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%)
}
`;
function PhotoGallery() {
return (
<Box
sx={(theme) => ({
borderRadius: 1,
overflow: 'hidden',
position: 'relative',
minWidth: '100%',
display: 'flex',
gap: 2,
my: 5,
'& > div': {
animation: `${scroll} 120s linear infinite`,
},
'&::before, &::after': {
background: `linear-gradient(to right, #FFF 0%, rgba(255, 255, 255, 0) 100%)`,
content: "''",
height: '100%',
position: 'absolute',
width: 200,
zIndex: 1,
pointerEvents: 'none',
},
'&::before': {
right: { xs: -64, sm: -20 },
top: 0,
transform: 'rotateZ(180deg)',
},
'&::after': {
left: { xs: -64, sm: -20 },
top: 0,
},
...theme.applyDarkStyles({
'&::before, &::after': {
background: `linear-gradient(to right, ${
(theme.vars || theme).palette.primaryDark[900]
} 0%, rgba(0, 0, 0, 0) 100%)`,
},
}),
})}
>
<ImageContainer>
{teamPhotos.map((item, index) => (
<Image
key={index}
src={item.img}
alt={item.title}
loading={index > 2 ? 'lazy' : undefined}
fetchPriority={index > 2 ? undefined : 'high'}
/>
))}
</ImageContainer>
<ImageContainer aria-hidden="true">
{/* aria-hidden is used here because this element is a copy from the above, meaning we want to hide it from screen readers. */}
{teamPhotos.map((item, index) => (
<Image key={index} src={item.img} alt={item.title} loading="lazy" />
))}
</ImageContainer>
</Box>
);
}
export default function AboutHero() {
return (
<Section cozy bg="gradient">
<SectionHeadline
alwaysCenter
overline="About us"
title={
<Typography variant="h2" component="h1">
We&apos;re on a mission to make <br />{' '}
<GradientText>building better UIs effortless</GradientText>
</Typography>
}
description="We give developers and designers the tools to bring stunning user interfaces to life with unrivaled speed and ease."
/>
<PhotoGallery />
<TeamStatistics />
</Section>
);
}

View File

@@ -0,0 +1,174 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import ForumRoundedIcon from '@mui/icons-material/ForumRounded';
import PeopleRoundedIcon from '@mui/icons-material/PeopleRounded';
import LocalAtmRoundedIcon from '@mui/icons-material/LocalAtmRounded';
import GradientText from 'docs/src/components/typography/GradientText';
import { Link } from '@mui/docs/Link';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import { GlowingIconContainer } from '@mui/docs/InfoCard';
function Widget({
children,
title,
icon,
}: {
children: React.ReactNode;
title: string;
icon: React.ReactElement<unknown>;
}) {
return (
<Paper
variant="outlined"
sx={(theme) => ({
p: 3,
height: '100%',
display: 'flex',
flexDirection: 'column',
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
...theme.applyDarkStyles({
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
}),
})}
>
<GlowingIconContainer icon={icon} />
<Typography
component="h3"
variant="body2"
sx={{ fontWeight: 'bold', color: 'text.primary', mt: 2, mb: 0.5 }}
>
{title}
</Typography>
{children}
</Paper>
);
}
export default function HowToSupport() {
return (
<Section cozy>
<SectionHeadline
overline="Support us"
title={
<Typography variant="h2" sx={{ mb: 4 }}>
Learn how to support
<br /> <GradientText>MUI&apos;s growth</GradientText>
</Typography>
}
description=""
/>
<Grid container spacing={3}>
<Grid size={{ xs: 12, sm: 6, md: 4 }}>
<Widget
icon={<ForumRoundedIcon fontSize="small" color="primary" />}
title="Give feedback"
>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>
Tell us what and where we can improve or share your happy moments with us! You can
also up or downvote any page on our documentation. <br />
<br /> And lastly, from time to time, we send our community a survey for more
structured feedback, you&apos;re always invited to participate to share your thoughts.
</Typography>
<Button
component="a"
size="small"
variant="outlined"
fullWidth
href="https://github.com/mui/material-ui/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc"
endIcon={<KeyboardArrowRightRounded />}
sx={{ mt: 'auto' }}
>
Leave your feedback{' '}
</Button>
</Widget>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4 }}>
<Widget
icon={<PeopleRoundedIcon fontSize="small" color="primary" />}
title="Join the community"
>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>
Become a member of a huge community of developers supporting MUI. You can:
</Typography>
<Box component="ul" sx={{ typography: 'body2', color: 'text.secondary', pl: 2, mb: 2 }}>
<li>
Add new features by{' '}
<Link href="https://github.com/mui/material-ui/blob/HEAD/CONTRIBUTING.md#your-first-pull-request">
submitting a pull request
</Link>
.
</li>
<li>
Fix bugs or{' '}
<Link href="https://github.com/mui/material-ui/tree/HEAD/docs">
improve our documentation
</Link>
.
</li>
<li>
Help others by reviewing and commenting on existing{' '}
<Link href="https://github.com/mui/material-ui/pulls">PRs</Link> and{' '}
<Link href="https://github.com/mui/material-ui/issues">issues</Link>.
</li>
<li>
Help <Link href="https://crowdin.com/project/material-ui-docs">translate</Link> the
documentation.
</li>
<li>
Answer questions on{' '}
<Link href="https://stackoverflow.com/questions/tagged/material-ui">
Stack&nbsp;Overflow
</Link>
.
</li>
</Box>
<Button
component="a"
size="small"
variant="outlined"
fullWidth
href="https://github.com/mui/material-ui"
endIcon={<KeyboardArrowRightRounded />}
sx={{ mt: 'auto' }}
>
See the repository
</Button>
</Widget>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4 }}>
<Widget
icon={<LocalAtmRoundedIcon fontSize="small" color="primary" />}
title="Support us financially"
>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>
If you use MUI in a commercial project and would like to support its continued
development by becoming a Sponsor, or in a side or hobby project and would like to
become a Backer, you can do so through {'Open Collective'}.
<br />
<br />
All funds donated are managed transparently, and Sponsors receive recognition in the
README and on the MUI home page.
</Typography>
<Button
component="a"
size="small"
variant="outlined"
fullWidth
href="https://opencollective.com/mui-org"
endIcon={<KeyboardArrowRightRounded />}
sx={{ mt: 'auto' }}
>
{'See Open Collective'}
</Button>
</Widget>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,139 @@
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import { Link } from '@mui/docs/Link';
import GradientText from 'docs/src/components/typography/GradientText';
import ROUTES from 'docs/src/route';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
const values = [
{
title: 'User-obsessed 💙',
description: "We never lose sight of who we're serving and why.",
lightIcon: 'url(/static/branding/about/illustrations/community-light.svg)',
darkIcon: 'url(/static/branding/about/illustrations/community-dark.svg)',
width: 92,
height: 84,
},
{
title: 'Keep it simple 🚫',
description: "We're so not corporate—and we like it that way.",
lightIcon: 'url(/static/branding/about/illustrations/bureaucracy-light.svg)',
darkIcon: 'url(/static/branding/about/illustrations/bureaucracy-dark.svg)',
width: 81,
height: 94,
},
{
title: 'Chase "better" 🌱',
description: "We're driven by an unending desire to improve.",
lightIcon: 'url(/static/branding/about/illustrations/better-light.svg)',
darkIcon: 'url(/static/branding/about/illustrations/better-dark.svg)',
width: 89,
height: 89,
},
{
title: 'Trust and deliver together 🚀',
description: 'We choose to cultivate unity as the core of achievement.',
lightIcon: 'url(/static/branding/about/illustrations/trust-light.svg)',
darkIcon: 'url(/static/branding/about/illustrations/trust-dark.svg)',
width: 75,
height: 92,
},
];
export default function OurValues() {
return (
<Section cozy>
<SectionHeadline
overline="Our values"
title={
<Typography variant="h2">
The MUI <GradientText>team pact</GradientText>
</Typography>
}
description="The MUI team pact describes the values we embody as a company, which help guide us toward the experiences and results we aim to deliver."
/>
<Button
component={Link}
noLinkStyle
href={ROUTES.handbook}
endIcon={<KeyboardArrowRightRounded fontSize="small" />}
variant="contained"
sx={{ width: { xs: '100%', sm: 'fit-content' } }}
>
View our handbook
</Button>
<Grid container spacing={3} sx={{ mt: { xs: 1, sm: 2 } }}>
{values.map(({ title, description, darkIcon, lightIcon, height, width }) => (
<Grid key={title} size={{ xs: 12, md: 3 }}>
<Paper
variant="outlined"
sx={(theme) => ({
p: 2.5,
height: '100%',
display: 'flex',
flexDirection: 'column',
gap: 1.5,
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
...theme.applyDarkStyles({
bgcolor: 'primaryDark.900',
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
borderColor: 'primaryDark.700',
}),
})}
>
<Box sx={{ height: 94 }}>
<Box
sx={[
{
width,
height,
},
(theme) => ({
background: `${lightIcon}`,
...theme.applyDarkStyles({
background: `${darkIcon}`,
}),
}),
]}
/>
</Box>
<Box sx={{ flexGrow: 1 }}>
<Typography
component="h3"
variant="body2"
sx={[
{
fontWeight: 'semiBold',
},
(theme) => ({
mb: 0.5,
color: (theme.vars || theme).palette.text.primary,
'&::first-letter': {
color: (theme.vars || theme).palette.primary.main,
},
...theme.applyDarkStyles({
'&::first-letter': {
color: (theme.vars || theme).palette.primary[400],
},
}),
}),
]}
>
{title}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{description}
</Typography>
</Box>
</Paper>
</Grid>
))}
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,427 @@
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Container from '@mui/material/Container';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import Grid from '@mui/material/Grid';
import Paper, { PaperProps } from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Tooltip from '@mui/material/Tooltip';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import XIcon from '@mui/icons-material/X';
import GitHubIcon from '@mui/icons-material/GitHub';
import LinkedInIcon from '@mui/icons-material/LinkedIn';
import { Link } from '@mui/docs/Link';
import ROUTES from 'docs/src/route';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import teamMembers from 'docs/data/about/teamMembers.json';
// The teamMembers.json file should be synced with `pnpm docs:sync-team`.
interface Profile {
/**
* The display name of the person.
* This is different from the full name (legal name).
*/
name: string;
/**
* Role, what are you working on?
*/
title: string;
/**
* Country where you live in, ISO 3166-1.
*/
locationCountry: string; // https://flagpedia.net/download/api
/**
* Image URL.
*/
src?: string;
/**
* Lives in.
*/
location?: string;
/**
* Short summary about you.
*/
about?: string;
github?: string;
twitter?: string;
linkedin?: string;
}
function Person(props: Profile & { sx?: PaperProps['sx'] }) {
return (
<Paper variant="outlined" sx={{ p: 2, height: '100%', ...props.sx }}>
<Box
sx={{
display: 'flex',
alignItems: 'flex-start',
flexWrap: 'wrap',
'& > div': { minWidth: 'clamp(0px, (150px - 100%) * 999 ,100%)' },
}}
>
<Tooltip
title={props.location || false}
placement="right-end"
describeChild
PopperProps={{
popperOptions: {
modifiers: [
{
name: 'offset',
options: {
offset: [3, 2],
},
},
],
},
}}
>
<Box sx={{ position: 'relative', display: 'inline-block' }}>
<Avatar
variant="rounded"
imgProps={{
width: '70',
height: '70',
loading: 'lazy',
}}
src={props.src}
alt={props.name}
{...(props.src?.startsWith('https://avatars.githubusercontent.com') && {
src: `${props.src}?s=70`,
srcSet: `${props.src}?s=140 2x`,
})}
sx={(theme) => ({
width: 70,
height: 70,
borderRadius: 1,
border: '1px solid',
borderColor: 'grey.100',
backgroundColor: 'primary.50',
...theme.applyDarkStyles({
backgroundColor: 'primary.900',
borderColor: 'primaryDark.500',
}),
})}
/>
<Box
sx={(theme) => ({
width: 24,
height: 24,
display: 'flex',
justifyContent: 'center',
position: 'absolute',
bottom: 0,
right: 0,
backgroundColor: '#FFF',
borderRadius: 40,
border: '2px solid',
borderColor: 'primary.50',
boxShadow: '0px 2px 6px rgba(0, 0, 0, 0.1)',
transform: 'translateX(50%)',
overflow: 'hidden',
...theme.applyDarkStyles({
borderColor: 'primary.200',
}),
})}
>
<img
loading="lazy"
height="20"
width="40"
src={`https://flagcdn.com/${props.locationCountry}.svg`}
alt=""
/>
</Box>
</Box>
</Tooltip>
<Box sx={{ mt: -0.5, mr: -0.5, ml: 'auto' }}>
{props.github && (
<IconButton
aria-label={`${props.name} GitHub profile`}
component="a"
href={`https://github.com/${props.github}`}
target="_blank"
rel="noopener"
>
<GitHubIcon fontSize="small" sx={{ color: 'grey.500' }} />
</IconButton>
)}
{props.twitter && (
<IconButton
aria-label={`${props.name} X profile`}
component="a"
href={`https://x.com/${props.twitter}`}
target="_blank"
rel="noopener"
>
<XIcon fontSize="small" sx={{ color: 'grey.500' }} />
</IconButton>
)}
{props.linkedin && (
<IconButton
aria-label={`${props.name} LinkedIn profile`}
component="a"
href={`https://www.linkedin.com/${props.linkedin}`}
target="_blank"
rel="noopener"
>
<LinkedInIcon fontSize="small" sx={{ color: 'grey.500' }} />
</IconButton>
)}
</Box>
</Box>
<Typography variant="body2" sx={{ fontWeight: 'bold', mt: 2, mb: 0.5 }}>
{props.name}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{props.title}
</Typography>
{props.about && <Divider sx={{ my: 1.5 }} />}
{props.about && (
<Typography variant="body2" sx={{ color: 'text.tertiary' }}>
{props.about}
</Typography>
)}
</Paper>
);
}
const contributors = [
{
name: 'Sebastian Silbermann',
github: 'eps1lon',
title: 'Material UI, everything Open Source',
location: 'Berlin, Germany',
locationCountry: 'de',
src: 'https://avatars.githubusercontent.com/u/12292047',
twitter: 'sebsilbermann',
},
{
name: 'Ryan Cogswell',
github: 'ryancogswell',
title: 'Stack Overflow top contributor',
location: 'Minnesota, United States',
locationCountry: 'us',
src: 'https://avatars.githubusercontent.com/u/287804',
},
{
name: 'Zeeshan Tamboli',
github: 'ZeeshanTamboli',
location: 'Pune, India',
locationCountry: 'in',
title: 'Material UI, MUI X',
src: 'https://avatars.githubusercontent.com/u/20900032',
twitter: 'ZeeshanTamboli',
linkedin: 'in/zeeshantamboli',
},
{
name: 'Sai Chand',
github: 'sai6855',
location: 'Hyderabad, India',
locationCountry: 'in',
title: 'Material UI, MUI X',
src: 'https://avatars.githubusercontent.com/u/60743144',
twitter: 'UrsSaichand',
linkedin: 'in/sai-chand-yamsani',
},
];
const emeriti = [
{
name: 'Hai Nguyen',
github: 'hai-cea',
twitter: 'haicea',
title: 'Material UI, v0.x creator',
location: 'Dallas, US',
locationCountry: 'us',
src: 'https://avatars.githubusercontent.com/u/2007468',
},
{
name: 'Nathan Marks',
github: 'nathanmarks',
title: 'Material UI, v1.x co-creator',
location: 'Toronto, CA',
locationCountry: 'ca',
src: 'https://avatars.githubusercontent.com/u/4420103',
},
{
name: 'Kevin Ross',
github: 'rosskevin',
twitter: 'rosskevin',
title: 'Material UI, flow',
location: 'Franklin, US',
locationCountry: 'us',
src: 'https://avatars.githubusercontent.com/u/136564',
},
{
name: 'Sebastian Sebald',
github: 'sebald',
twitter: 'sebastiansebald',
title: 'Material UI',
location: 'Freiburg, Germany',
locationCountry: 'de',
src: 'https://avatars.githubusercontent.com/u/985701',
},
{
name: 'Ken Gregory',
github: 'kgregory',
title: 'Material UI',
location: 'New Jersey, US',
locationCountry: 'us',
src: 'https://avatars.githubusercontent.com/u/3155127',
},
{
name: 'Tom Crockett',
github: 'pelotom',
twitter: 'pelotom',
title: 'Material UI',
location: 'Los Angeles, US',
locationCountry: 'us',
src: 'https://avatars.githubusercontent.com/u/128019',
},
{
name: 'Maik Marschner',
github: 'leMaik',
twitter: 'leMaikOfficial',
title: 'Material UI',
location: 'Hannover, Germany',
locationCountry: 'de',
src: 'https://avatars.githubusercontent.com/u/5544859',
},
{
name: 'Oleg Slobodskoi',
github: 'kof',
twitter: 'oleg008',
title: 'Material UI, JSS',
location: 'Berlin, Germany',
locationCountry: 'de',
src: 'https://avatars.githubusercontent.com/u/52824',
},
{
name: 'Dmitriy Kovalenko',
github: 'dmtrKovalenko',
twitter: 'goose_plus_plus',
title: 'MUI X Date Pickers',
location: 'Kharkiv, Ukraine',
locationCountry: 'ua',
src: 'https://avatars.githubusercontent.com/u/16926049',
},
{
name: 'Josh Wooding',
github: 'joshwooding',
twitter: 'JoshWooding_',
title: 'Material UI, J.P. Morgan',
location: 'London, UK',
locationCountry: 'gb',
src: 'https://avatars.githubusercontent.com/u/12938082',
},
{
name: 'Yan Lee',
github: 'AGDholo',
title: 'Chinese docs',
location: 'China',
locationCountry: 'cn',
src: 'https://avatars.githubusercontent.com/u/13300332',
},
{
name: 'Danica Shen',
github: 'DDDDDanica',
title: 'Chinese docs',
location: 'Ireland',
locationCountry: 'ie',
src: 'https://avatars.githubusercontent.com/u/12678455',
},
];
export default function Team() {
return (
<React.Fragment>
<Section cozy>
<Box sx={{ my: 4, display: 'flex', flexDirection: 'column' }}>
<SectionHeadline
overline="Team"
title={
<Typography variant="h2" id="muiers">
Meet the <GradientText>MUIers</GradientText>
</Typography>
}
description="Contributing from all corners of the world, MUI is a global, fully-remote team & community."
/>
<Button
component={Link}
noLinkStyle
href={ROUTES.careers}
endIcon={<KeyboardArrowRightRounded fontSize="small" />}
variant="contained"
sx={{ width: { xs: '100%', sm: 'fit-content' } }}
>
View careers
</Button>
</Box>
<Grid container spacing={2}>
{(teamMembers as Array<Profile>).map((profileJson) => {
const profile = {
src: `/static/branding/about/${profileJson.name
.split(' ')
.map((x) => x.toLowerCase())
.join('-')}.png`,
...profileJson,
};
return (
<Grid key={profile.name} size={{ xs: 12, sm: 6, md: 3 }}>
<Person {...profile} />
</Grid>
);
})}
</Grid>
</Section>
<Divider />
{/* Community contributors */}
<Box data-mui-color-scheme="dark" sx={{ bgcolor: 'common.black' }}>
<Container sx={{ py: { xs: 4, sm: 8 } }}>
<Typography
component="h3"
variant="h4"
sx={{ color: 'primary.200', fontWeight: 'semiBold' }}
>
Community contributors
</Typography>
<Typography sx={{ color: 'text.secondary', maxWidth: { md: 500 } }}>
Special members of the community deserve a shout-out for their ever-lasting impact on
MUI&apos;s open-source projects.
</Typography>
<Grid container spacing={2} mt={2}>
{contributors.map((profile) => (
<Grid key={profile.name} size={{ xs: 12, sm: 6, md: 3 }}>
<Person {...profile} sx={{ bgcolor: 'primaryDark.600' }} />
</Grid>
))}
</Grid>
<Divider sx={{ my: { xs: 2, sm: 6 } }} />
<Typography
component="h3"
variant="h4"
sx={{ color: 'warning.300', fontWeight: 'semiBold' }}
>
Community emeriti
</Typography>
<Typography sx={{ color: 'text.secondary', maxWidth: { md: 500 } }}>
We honor some no-longer-active core team members who have made valuable contributions in
the past. They advise us from time to time.
</Typography>
<Grid container spacing={2} mt={2}>
{emeriti.map((profile) => (
<Grid key={profile.name} size={{ xs: 12, sm: 6, md: 3 }}>
<Person {...profile} sx={{ bgcolor: 'primaryDark.600' }} />
</Grid>
))}
</Grid>
</Container>
</Box>
</React.Fragment>
);
}

View File

@@ -0,0 +1,40 @@
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
const data = [
{ number: '2014', metadata: 'The starting year' },
{ number: '100%', metadata: 'Remote global team' },
{ number: '20+', metadata: 'Countries represented' },
];
export default function TeamStatistics() {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2 }}>
{data.map((item) => (
<Box key={item.number} sx={{ height: '100%', width: { xs: '100%', sm: 200 } }}>
<Typography
component="p"
variant="h4"
sx={[
{
fontWeight: 'bold',
},
(theme) => ({
textAlign: { xs: 'left', sm: 'center' },
color: 'primary.main',
...theme.applyDarkStyles({
color: 'primary.200',
}),
}),
]}
>
{item.number}
</Typography>
<Typography sx={{ color: 'text.secondary', textAlign: { xs: 'left', sm: 'center' } }}>
{item.metadata}
</Typography>
</Box>
))}
</Box>
);
}

View File

@@ -0,0 +1,54 @@
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
import KeyboardArrowLeftRounded from '@mui/icons-material/KeyboardArrowLeftRounded';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
export default function ArrowButton({
direction,
...props
}: { direction: 'left' | 'right' } & IconButtonProps) {
const label = {
left: 'Previous',
right: 'Next',
};
return (
<IconButton
size="small"
aria-label={label[direction]}
{...props}
sx={[
{
border: '1px solid',
color: 'primary.main',
borderColor: 'grey.200',
'&:hover': {
borderColor: 'grey.300',
},
'&.Mui-disabled': {
opacity: 0.5,
color: 'grey.700',
borderColor: 'grey.300',
},
'& + .MuiIconButton-root': {
ml: 2,
},
},
(theme) =>
theme.applyDarkStyles({
color: 'primary.200',
borderColor: 'primaryDark.400',
'&:hover': {
borderColor: 'primary.300',
},
'&.Mui-disabled': {
color: 'primaryDark.200',
borderColor: 'primaryDark.400',
},
}),
...(Array.isArray(props.sx) ? props.sx : [props.sx]),
]}
>
{direction === 'left' && <KeyboardArrowLeftRounded fontSize="small" />}
{direction === 'right' && <KeyboardArrowRightRounded fontSize="small" />}
</IconButton>
);
}

View File

@@ -0,0 +1,76 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import Stack from '@mui/material/Stack';
import Card from '@mui/material/Card';
import CardMedia from '@mui/material/CardMedia';
import Typography from '@mui/material/Typography';
import Chip from '@mui/material/Chip';
import { Link } from '@mui/docs/Link';
interface ComponentShowcaseCardProps {
imgLoading?: 'eager';
link: string;
md1?: React.ReactNode;
md2?: React.ReactNode;
md3?: React.ReactNode;
name: string;
noGuidelines?: React.ReactNode;
srcDark: string;
srcLight: string;
}
export default function ComponentShowcaseCard(props: ComponentShowcaseCardProps) {
const { link, srcLight, srcDark, name, md1, md2, md3, noGuidelines, imgLoading = 'lazy' } = props;
// Fix overloading with prefetch={false}, only prefetch on hover.
return (
<Card
component={Link}
noLinkStyle
prefetch={false}
variant="outlined"
href={link}
sx={(theme) => ({
height: '100%',
display: 'flex',
flexDirection: 'column',
borderRadius: 1,
borderColor: 'divider',
...theme.applyDarkStyles({
backgroundColor: `${alpha(theme.palette.primaryDark[700], 0.1)}`,
borderColor: 'primaryDark.700',
}),
})}
>
<CardMedia
component="img"
alt=""
loading={imgLoading}
image={srcLight}
sx={(theme) => ({
aspectRatio: '16 / 9',
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
borderBottom: '1px solid',
borderColor: 'divider',
...theme.applyDarkStyles({
content: `url(${srcDark})`,
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
borderColor: 'primaryDark.700',
}),
})}
/>
<Stack direction="row" sx={{ justifyContent: 'space-between', px: 2, py: 1.5 }}>
<Typography component="h2" variant="body2" sx={{ fontWeight: 'semiBold' }}>
{name}
</Typography>
<Stack direction="row" spacing={0.5} useFlexGap>
{md1 && <Chip label="MD1" size="small" variant="outlined" color="primary" />}
{md2 && <Chip label="MD2" size="small" variant="outlined" color="primary" />}
{md3 && <Chip label="MD3" size="small" variant="outlined" color="success" />}
{noGuidelines && (
<Chip label="No guidelines" size="small" variant="outlined" color="info" />
)}
</Stack>
</Stack>
</Card>
);
}

View File

@@ -0,0 +1,82 @@
import * as React from 'react';
import Box, { BoxProps } from '@mui/material/Box';
const FrameDemo = React.forwardRef<HTMLDivElement, BoxProps>(function FrameDemo(props, ref) {
return (
<Box
ref={ref}
{...props}
sx={[
(theme) => ({
position: 'relative',
border: '1px solid',
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
borderColor: 'grey.100',
...theme.applyDarkStyles({
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
borderColor: 'primaryDark.700',
}),
}),
...(Array.isArray(props.sx) ? props.sx : [props.sx]),
]}
/>
);
});
const FrameInfo = React.forwardRef<HTMLDivElement, BoxProps>(function FrameInfo(props, ref) {
return (
<Box
ref={ref}
{...props}
sx={{
p: 2,
overflow: 'clip',
position: 'relative',
colorScheme: 'dark',
color: '#fff',
bgcolor: 'common.black',
border: '1px solid',
borderColor: 'primaryDark.700',
borderTop: 0,
borderBottomLeftRadius: 12,
borderBottomRightRadius: 12,
...props.sx,
}}
/>
);
});
const Frame = React.forwardRef<HTMLDivElement, BoxProps>(function Frame(
{ sx, ...props }: BoxProps,
ref,
) {
return (
<Box
ref={ref}
{...props}
sx={[
{
display: 'flex',
flexDirection: 'column',
'& > div:first-of-type': {
borderTopLeftRadius: '12px',
borderTopRightRadius: '12px',
},
'& > div:last-of-type': {
borderBottomLeftRadius: '12px',
borderBottomRightRadius: '12px',
},
},
...(Array.isArray(sx) ? sx : [sx]),
]}
/>
);
}) as ReturnType<typeof React.forwardRef<HTMLDivElement, BoxProps>> & {
Demo: typeof FrameDemo;
Info: typeof FrameInfo;
};
Frame.Demo = FrameDemo;
Frame.Info = FrameInfo;
export default Frame;

View File

@@ -0,0 +1,106 @@
import * as React from 'react';
import ButtonBase, { ButtonBaseProps } from '@mui/material/ButtonBase';
import { alpha } from '@mui/material/styles';
export default function Highlighter({
disableBorder = false,
selected = false,
sx,
...props
}: {
disableBorder?: boolean;
selectedBg?: 'white' | 'comfort';
selected?: boolean;
} & ButtonBaseProps) {
const ref = React.useRef<HTMLButtonElement>(null);
return (
<ButtonBase
component="span"
ref={ref}
{...props}
onClick={(event: any) => {
if (ref.current) {
ref.current.scrollIntoView({ block: 'nearest' });
}
if (props.onClick) {
props.onClick(event);
}
}}
onFocusVisible={(event) => {
if (ref.current) {
ref.current.scrollIntoView({ block: 'nearest' });
}
if (props.onFocusVisible) {
props.onFocusVisible(event);
}
}}
sx={[
(theme) => ({
justifyContent: 'flex-start',
textAlign: 'left',
alignItems: 'center',
borderRadius: 1,
height: '100%',
border: '1px solid transparent',
transitionProperty: 'all',
transitionDuration: '100ms',
color: 'primary.300',
...((!disableBorder || selected) && {
borderColor: 'grey.100',
}),
...(selected && {
bgcolor: `${alpha(theme.palette.primary[50], 0.5)}`,
borderColor: 'primary.300',
boxShadow: `${alpha(theme.palette.primary[100], 0.5)} 0 -3px 1px inset, ${alpha(
theme.palette.primary[100],
0.3,
)} 0 2px 4px 0`,
color: 'primary.500',
}),
...(!selected && {
'&:hover': {
bgcolor: 'primary.50',
borderColor: 'primary.100',
'@media (hover: none)': {
bgcolor: 'transparent',
},
},
'&:focus': {
bgcolor: 'transparent',
},
}),
...theme.applyDarkStyles({
color: 'primary.800',
...((!disableBorder || selected) && {
borderColor: alpha(theme.palette.primaryDark[600], 0.3),
}),
...(!selected && {
'&:hover': {
bgcolor: alpha(theme.palette.primary[900], 0.1),
borderColor: alpha(theme.palette.primary[800], 0.4),
'@media (hover: none)': {
bgcolor: 'transparent',
},
},
'&:focus': {
bgcolor: 'transparent',
},
}),
...(selected && {
bgcolor: alpha(theme.palette.primary[800], 0.2),
borderColor: alpha(theme.palette.primary[700], 0.8),
color: 'primary.300',
boxShadow: `${alpha(theme.palette.common.black, 0.2)} 0 -3px 1px inset, ${
theme.palette.primaryDark[900]
} 0 2px 3px 0`,
}),
}),
'&.Mui-disabled': {
opacity: 0.4,
},
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
/>
);
}

View File

@@ -0,0 +1,119 @@
import * as React from 'react';
import { useTheme } from '@mui/material/styles';
import Box, { BoxProps } from '@mui/material/Box';
import Typography from '@mui/material/Typography';
export function Group({
desktopColumns = 1,
rowLayout = false,
...props
}: { desktopColumns?: number; rowLayout?: boolean } & BoxProps) {
const theme = useTheme();
return (
<Box
{...props}
sx={{
overflow: 'auto',
maxWidth: rowLayout ? 'none' : { md: 500 },
display: { xs: 'grid', sm: rowLayout ? 'flex' : 'grid' },
justifyContent: { xs: 'start', sm: rowLayout ? 'center' : null },
gap: 1,
gridTemplateColumns: `repeat(${desktopColumns}, 1fr)`,
'@media (prefers-reduced-motion: no-preference)': {
scrollBehavior: 'smooth',
},
'& > *': {
minWidth: {
xs: desktopColumns === 1 ? 300 : 225,
sm: desktopColumns === 1 ? 400 : 225,
md: 'auto',
},
gridRow: { xs: 1, md: 'auto' },
},
[theme.breakpoints.down('md')]: {
mx: -3,
px: 3,
mb: -1.5,
pb: 2,
scrollSnapType: 'inline mandatory',
scrollPaddingLeft: 30,
scrollPaddingRight: 30,
'& > *': {
scrollSnapAlign: 'start',
},
'& > *:last-child': {
position: 'relative',
'&::after': {
// to create scroll spacing on the right edge
content: '""',
position: 'absolute',
blockSize: '100%',
inlineSize: 30,
insetBlockStart: 0,
insetInlineEnd: -30,
},
},
},
[theme.breakpoints.down('sm')]: {
mx: -2,
px: 2,
scrollPaddingLeft: 20,
scrollPaddingRight: 20,
'& > *:last-child:after': {
inlineSize: 20,
insetBlockStart: 0,
insetInlineEnd: -20,
},
},
...props.sx,
}}
/>
);
}
export default function Item({
description,
icon,
title,
smallerIconDistance = false,
...props
}: {
description?: string;
icon: React.ReactNode;
title: string;
smallerIconDistance?: boolean;
} & BoxProps) {
return (
<Box
{...props}
component="span"
sx={{
p: 2,
pr: smallerIconDistance ? 3 : 2,
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
alignItems: { xs: 'start', sm: 'center' },
gap: { xs: 2, sm: '14px' },
...props.sx,
}}
>
<Box component="span" sx={{ lineHeight: 0 }}>
{icon}
</Box>
<Box sx={{ flexWrap: 'wrap' }}>
<Typography color="text.primary" variant="body2" fontWeight="semiBold">
{title}
</Typography>
{description && (
<Typography
component="span"
variant="body2"
sx={{ color: 'text.secondary', fontWeight: 'regular', mt: 0.5 }}
>
{description}
</Typography>
)}
</Box>
</Box>
);
}

View File

@@ -0,0 +1,124 @@
import * as React from 'react';
import copy from 'clipboard-copy';
import { Link } from '@mui/docs/Link';
import { Portal } from '@mui/base/Portal';
import Box from '@mui/material/Box';
import Snackbar from '@mui/material/Snackbar';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import Slide from '@mui/material/Slide';
import TextFieldsRoundedIcon from '@mui/icons-material/TextFieldsRounded';
import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded';
import { RootSvgProps } from 'docs/src/icons/RootSvg';
import SvgMuiLogomark, {
muiSvgLogoString,
muiSvgWordmarkString,
} from 'docs/src/icons/SvgMuiLogomark';
interface LogoWithCopyMenuProps {
logo?: React.ComponentType<RootSvgProps>;
logoSvgString?: string;
wordmarkSvgString?: string;
marginLeft?: boolean;
}
export default function LogoWithCopyMenu({
logo: LogoSvg = SvgMuiLogomark,
logoSvgString = muiSvgLogoString,
wordmarkSvgString = muiSvgWordmarkString,
marginLeft,
}: LogoWithCopyMenuProps) {
const [contextMenu, setContextMenu] = React.useState<{
mouseX: number;
mouseY: number;
} | null>(null);
const handleContextMenu = (event: React.MouseEvent) => {
event.preventDefault();
setContextMenu(
contextMenu === null
? {
mouseX: event.clientX + 8,
mouseY: event.clientY - 8,
}
: null,
);
};
const handleClose = () => {
setContextMenu(null);
};
const [copied, setCopied] = React.useState(false);
const handleCopy = (svgSnippet: string) => {
setCopied(true);
copy(svgSnippet).then(() => {
setTimeout(() => setCopied(false), 3500);
handleClose();
});
};
return (
<React.Fragment>
<Box
component={Link}
href="/"
aria-label="Go to homepage"
onContextMenu={handleContextMenu}
sx={{
mr: 1,
ml: marginLeft ? 1.5 : undefined,
'& > svg': { m: '0 !important' }, // override the 2px margin-left coming from the Link component
}}
>
<LogoSvg height={28} width={28} />
</Box>
<Menu
open={contextMenu !== null}
onClose={handleClose}
anchorReference="anchorPosition"
anchorPosition={
contextMenu !== null ? { top: contextMenu.mouseY, left: contextMenu.mouseX } : undefined
}
sx={(theme) => ({
'& .MuiMenuItem-root': {
gap: 1,
'& path': {
fill: (theme.vars || theme).palette.text.tertiary,
color: (theme.vars || theme).palette.text.tertiary,
},
'&:hover, &:focus-visible': {
'& path': {
fill: (theme.vars || theme).palette.text.primary,
color: (theme.vars || theme).palette.text.primary,
},
},
},
})}
>
<MenuItem onClick={() => handleCopy(logoSvgString)}>
<LogoSvg height={16} width={18} />
Copy logo as SVG
</MenuItem>
<MenuItem onClick={() => handleCopy(wordmarkSvgString)}>
<TextFieldsRoundedIcon sx={{ fontSize: '18px' }} />
Copy wordmark as SVG
</MenuItem>
</Menu>
<Portal container={() => document.body}>
<Snackbar
open={copied}
onClose={handleClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
TransitionComponent={Slide}
message={
<Box sx={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<CheckCircleRoundedIcon sx={{ fontSize: '18px', color: 'success.main' }} />
Logo SVG copied to clipboard!
</Box>
}
/>
</Portal>
</React.Fragment>
);
}

View File

@@ -0,0 +1,61 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Button, { buttonClasses } from '@mui/material/Button';
interface MaterialVsCustomToggleProps {
customized: boolean;
setCustomized: React.Dispatch<boolean>;
}
export default function MaterialVsCustomToggle({
customized,
setCustomized,
}: MaterialVsCustomToggleProps) {
return (
<Box
sx={(theme) => ({
position: 'absolute',
top: 0,
left: 0,
right: 0,
p: 1.5,
display: 'flex',
gap: 1,
zIndex: 3,
background: `linear-gradient(to bottom, ${
(theme.vars || theme).palette.common.black
} 70%, transparent)`,
[`& .${buttonClasses.root}`]: {
borderRadius: 99,
padding: '1px 8px',
fontSize: theme.typography.pxToRem(12),
},
'& .MuiButton-outlinedPrimary': {
backgroundColor: alpha(theme.palette.primary[900], 0.5),
},
})}
>
<Button
size="small"
variant="outlined"
color={customized ? 'secondary' : 'primary'}
onClick={() => {
setCustomized(false);
}}
>
Material Design
</Button>
<Button
size="small"
variant="outlined"
color={customized ? 'primary' : 'secondary'}
onClick={() => {
setCustomized(true);
}}
>
Custom theme
</Button>
</Box>
);
}

View File

@@ -0,0 +1,81 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import ButtonBase, { ButtonBaseProps } from '@mui/material/ButtonBase';
import Typography from '@mui/material/Typography';
import AddCircleRoundedIcon from '@mui/icons-material/AddCircleRounded';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
export default (function More(props: ButtonBaseProps) {
const ref = React.useRef<HTMLButtonElement>(null);
return (
<ButtonBase
ref={ref}
{...props}
onClick={(event) => {
if (ref.current) {
ref.current.scrollIntoView({ block: 'nearest' });
}
if (props.onClick) {
props.onClick(event);
}
}}
onFocusVisible={(event) => {
if (ref.current) {
ref.current.scrollIntoView({ block: 'nearest' });
}
if (props.onFocusVisible) {
props.onFocusVisible(event);
}
}}
sx={[
(theme) => ({
p: 2,
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
cursor: 'pointer',
borderRadius: 1,
height: '100%',
border: '1px dashed',
transitionProperty: 'all',
transitionDuration: '150ms',
borderColor: 'grey.200',
'& * svg': { transition: '0.2s' },
'&:hover, &:focus': {
borderColor: 'primary.main',
bgcolor: alpha(theme.palette.primary[100], 0.4),
'* .chevron': { transform: 'translateX(2px)' },
'@media (hover: none)': {
bgcolor: 'transparent',
},
},
...theme.applyDarkStyles({
borderColor: `${alpha(theme.palette.primaryDark[400], 0.3)}`,
'&:hover, &:focus': {
bgcolor: alpha(theme.palette.primary[900], 0.4),
},
}),
}),
...(Array.isArray(props.sx) ? props.sx : [props.sx]),
]}
>
<Box component="span" sx={{ mr: 1, px: '3px', lineHeight: 0 }}>
<AddCircleRoundedIcon color="primary" fontSize="small" />
</Box>
<Typography
component="span"
variant="body2"
sx={{ color: 'primary.main', fontWeight: 'bold', width: '100%' }}
>
Much more{' '}
<KeyboardArrowRightRounded
className="chevron"
color="primary"
fontSize="small"
sx={{ verticalAlign: 'middle', ml: 'auto' }}
/>
</Typography>
</ButtonBase>
);
} as typeof ButtonBase);

View File

@@ -0,0 +1,110 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import Box, { BoxProps } from '@mui/material/Box';
import Button from '@mui/material/Button';
import { Link } from '@mui/docs/Link';
import IconButton from '@mui/material/IconButton';
import KeyboardArrowUpRounded from '@mui/icons-material/KeyboardArrowUpRounded';
import KeyboardArrowDownRounded from '@mui/icons-material/KeyboardArrowDownRounded';
export function AppearingInfoBox({
appeared,
children,
...props
}: { appeared: boolean; children: React.ReactNode } & BoxProps) {
const [hidden, setHidden] = React.useState(false);
return (
<Box
{...props}
sx={{
position: 'absolute',
bottom: 8,
left: 8,
right: 8,
zIndex: 3,
mx: -1,
background: ({ palette }) => alpha(palette.common.black, 0.9),
borderTop: '1px solid',
borderColor: hidden || !appeared ? 'transparent' : 'primaryDark.700',
transform: hidden || !appeared ? 'translateY(100%)' : 'translateY(0)',
transition: '0.2s',
...props.sx,
}}
>
<IconButton
size="small"
aria-label={hidden ? 'show' : 'hide'}
onClick={() => setHidden((bool) => !bool)}
sx={{
position: 'absolute',
zIndex: 2,
transition: '0.3s',
right: 16,
bottom: '100%',
transform: hidden || !appeared ? 'translateY(-10px)' : 'translateY(50%)',
opacity: appeared ? 1 : 0,
color: '#FFF',
backgroundColor: 'primary.500',
'&:hover': {
backgroundColor: 'primary.800',
},
}}
>
{hidden ? (
<KeyboardArrowUpRounded fontSize="small" />
) : (
<KeyboardArrowDownRounded fontSize="small" />
)}
</IconButton>
<Box sx={{ px: 2, py: 1.5 }}>{children}</Box>
</Box>
);
}
export default function MoreInfoBox({
primaryBtnLabel,
primaryBtnHref,
secondaryBtnLabel,
secondaryBtnHref,
...props
}: {
primaryBtnLabel: string;
primaryBtnHref: string;
secondaryBtnLabel: string;
secondaryBtnHref: string;
} & BoxProps) {
return (
<Box
data-mui-color-scheme="dark"
{...props}
sx={{
p: 1.5,
bottom: 0,
left: 0,
right: 0,
background: ({ palette }) => alpha(palette.primaryDark[800], 0.2),
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: { xs: 1.5, sm: 1 },
borderTop: '1px solid',
borderColor: 'divider',
zIndex: 3,
...props.sx,
}}
>
<Button component={Link} noLinkStyle size="small" variant="contained" href={primaryBtnHref}>
{primaryBtnLabel}
</Button>
<Button
component={Link}
noLinkStyle
size="small"
variant="outlined"
color="secondary"
href={secondaryBtnHref}
>
{secondaryBtnLabel}
</Button>
</Box>
);
}

View File

@@ -0,0 +1,92 @@
/* eslint-disable react/prop-types */
import * as React from 'react';
import copy from 'clipboard-copy';
import { SxProps } from '@mui/system';
import { styled, alpha, Theme } from '@mui/material/styles';
import ContentCopyRounded from '@mui/icons-material/ContentCopyRounded';
import CheckRounded from '@mui/icons-material/CheckRounded';
const Button = styled('button')(({ theme }) => ({
boxSizing: 'border-box',
minWidth: 64,
margin: 0,
marginTop: 16,
cursor: 'copy',
padding: 0,
position: 'relative',
display: 'inline-flex',
alignItems: 'flex-start',
justifyContent: 'center',
verticalAlign: 'middle',
gap: 8,
outline: 0,
border: 0,
boxShadow: 'none',
backgroundColor: 'transparent',
fontFamily: theme.typography.fontFamilyCode,
fontSize: theme.typography.pxToRem(12),
textDecoration: 'none',
textTransform: 'initial',
lineHeight: 1.5,
letterSpacing: 0,
transition: theme.transitions.create('color', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shortest,
}),
WebkitTapHighlightColor: 'transparent',
WebkitFontSmoothing: 'subpixel-antialiased',
color: (theme.vars || theme).palette.text.tertiary,
'&:hover, &:focus-visible': {
color: (theme.vars || theme).palette.primary.main,
'@media (hover: none)': {
color: (theme.vars || theme).palette.text.tertiary,
},
},
'& svg': {
display: 'inline-block',
position: 'absolute',
right: -24,
top: 1,
opacity: 0,
transition: theme.transitions.create('opacity', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shortest,
}),
},
'&:focus, &:hover svg': {
opacity: 1,
},
'&:focus-visible': {
outline: `3px solid ${alpha(theme.palette.primary[500], 0.5)}`,
outlineOffset: '2px',
},
}));
export default function NpmCopyButton(
props: React.HTMLAttributes<HTMLButtonElement> & { installation: string; sx?: SxProps<Theme> },
) {
const { installation, onClick, sx, ...other } = props;
const [copied, setCopied] = React.useState(false);
const handleCopy = () => {
setCopied(true);
copy(installation).then(() => {
setTimeout(() => setCopied(false), 2000);
});
};
return (
<Button
onClick={(event: any) => {
handleCopy();
onClick?.(event);
}}
{...other}
>
$ {installation}
{copied ? (
<CheckRounded color="inherit" sx={{ fontSize: 15 }} />
) : (
<ContentCopyRounded color="inherit" sx={{ fontSize: 15 }} />
)}
</Button>
);
}

View File

@@ -0,0 +1,15 @@
import * as React from 'react';
import Fade, { FadeProps } from '@mui/material/Fade';
export default function FadeDelay({ delay, ...props }: { delay: number } & FadeProps) {
const [fadeIn, setFadeIn] = React.useState(false);
React.useEffect(() => {
const time = setTimeout(() => {
setFadeIn(true);
}, delay);
return () => {
clearTimeout(time);
};
}, [delay]);
return <Fade in={fadeIn} timeout={1000} {...props} />;
}

View File

@@ -0,0 +1,50 @@
import * as React from 'react';
import { styled, alpha, SxProps } from '@mui/material/styles';
const FlashCodeRoot = styled('div')(({ theme }) => ({
borderRadius: 2,
pointerEvents: 'none',
position: 'absolute',
left: -1, // Have at least a 1px gap between the text and the border of the FlashCode.
right: 0,
top: `calc(var(--Flashcode-lineHeight) * 1.5 * var(--Flashcode-startLine))`,
height: `calc(var(--Flashcode-lineHeight) * 1.5 * (var(--Flashcode-endLine) - var(--Flashcode-startLine) + 1))`,
transition: '0.3s',
...theme.typography.caption,
backgroundColor: theme.vars
? `rgba(${theme.vars.palette.primary.mainChannel} / 0.15)`
: alpha(theme.palette.primary.main, 0.1),
border: '1px solid',
borderColor: (theme.vars || theme).palette.primary.dark,
}));
const FlashCode = React.forwardRef(function FlashCode(
props: React.JSX.IntrinsicElements['div'] & {
sx?: SxProps;
endLine?: number;
startLine?: number;
lineHeight?: number | string;
},
ref: React.ForwardedRef<HTMLDivElement>,
) {
const { children, startLine = 0, endLine = startLine, lineHeight = '0.75rem', ...other } = props;
return (
<FlashCodeRoot
ref={ref}
{...other}
style={{
...{
'--Flashcode-lineHeight': lineHeight,
'--Flashcode-startLine': startLine,
'--Flashcode-endLine': endLine,
},
...other.style,
}}
>
{children}
</FlashCodeRoot>
);
});
export default FlashCode;

View File

@@ -0,0 +1,27 @@
import Box, { BoxProps } from '@mui/material/Box';
export default function Slide({
animationName,
keyframes,
...props
}: BoxProps & { animationName: string; keyframes: Record<string, object> }) {
return (
<Box
{...props}
sx={{
display: 'grid',
gridTemplateRows: 'min-content',
gap: { xs: 2, sm: 4, md: 8 },
width: 'min-content',
animation: `${animationName} 30s ease-out forwards`,
'@media (prefers-reduced-motion)': {
animation: 'none',
},
[`@keyframes ${animationName}`]: {
...keyframes,
},
...props.sx,
}}
/>
);
}

View File

@@ -0,0 +1,95 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import { Link } from '@mui/docs/Link';
import FEATURE_TOGGLE from 'docs/src/featureToggle';
import PageContext from 'docs/src/modules/components/PageContext';
import { convertProductIdToName } from 'docs/src/modules/components/AppSearch';
const showSurveyMessage = false;
function isBlackFriday() {
const today = Date.now();
const start = new Date('2024-11-25').getTime();
const end = new Date('2024-12-07T23:59:59Z').getTime();
return today > start && today < end;
}
let hadHydrated = false;
export default function AppFrameBanner() {
if (!FEATURE_TOGGLE.enable_docsnav_banner) {
return null;
}
// eslint-disable-next-line react-hooks/rules-of-hooks
const [mounted, setMounted] = React.useState(hadHydrated);
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
hadHydrated = true;
setMounted(true);
}, []);
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/rules-of-hooks -- FEATURE_TOGGLE never changes
const pageContext = React.useContext(PageContext);
const productName = convertProductIdToName(pageContext) || 'MUI';
let message = '';
let href = '';
if (showSurveyMessage) {
message = `🚀 Influence ${productName}'s 2025 roadmap! Participate in the latest Developer Survey`;
href = 'https://tally.so/r/mObbvk?source=website';
} else if (mounted && isBlackFriday()) {
message = `Black Friday is here! Don't miss out on the best offers of the year.`;
href = 'https://mui.com/store/bundles/?deal=black-friday&from=docs';
}
if (process.env.NODE_ENV !== 'production') {
if (message.length > 100) {
throw new Error(
`Docs-infra: AppFrameBanner message is too long. It will overflow on smaller screens.`,
);
}
}
if (message === '' || href === '') {
return null;
}
return (
<Link
href={href}
target="_blank"
variant="caption"
sx={[
(theme) => ({
padding: theme.spacing('6px', 1.5),
display: { xs: 'none', md: 'block' },
fontWeight: 'medium',
textWrap: 'nowrap',
maxHeight: '34px',
backgroundColor: alpha(theme.palette.primary[50], 0.8),
border: '1px solid',
borderColor: (theme.vars || theme).palette.divider,
borderRadius: 1,
transition: 'all 150ms ease',
'&:hover, &:focus-visible': {
backgroundColor: alpha(theme.palette.primary[100], 0.4),
borderColor: (theme.vars || theme).palette.primary[200],
},
}),
(theme) =>
theme.applyDarkStyles({
backgroundColor: alpha(theme.palette.primary[900], 0.15),
'&:hover, &:focus-visible': {
backgroundColor: alpha(theme.palette.primary[900], 0.4),
borderColor: (theme.vars || theme).palette.primary[900],
},
}),
]}
>
{message}
</Link>
);
}

View File

@@ -0,0 +1,89 @@
import * as React from 'react';
import Typography from '@mui/material/Typography';
import { Theme } from '@mui/material/styles';
import { Link } from '@mui/docs/Link';
import ROUTES from 'docs/src/route';
import FEATURE_TOGGLE from 'docs/src/featureToggle';
const linkStyleOverrides = (theme: Theme) => ({
color: 'inherit',
textDecorationColor: 'currentColor',
'&:hover': {
color: (theme.vars || theme).palette.primary[200],
},
...theme.applyDarkStyles({
color: 'inherit',
'&:hover': {
color: (theme.vars || theme).palette.primary[200],
},
}),
});
function getSurveyMessage() {
return (
<React.Fragment>
{`🚀 Influence MUI's 2025 roadmap! Participate in the latest`}
&nbsp;
<Link
href="https://tally.so/r/mObbvk?source=website"
target="_blank"
underline="always"
sx={linkStyleOverrides}
>
Developer Survey
</Link>
</React.Fragment>
);
}
function getDefaultHiringMessage() {
return (
<React.Fragment>
🚀&#160;&#160;We&apos;re hiring a Designer, Full-stack Engineer, React Community Engineer, and
more!&nbsp;&#160;
<Link
// Fix me!
href={ROUTES.careers}
target="_blank"
underline="always"
sx={linkStyleOverrides}
>
Check the careers page
</Link>
</React.Fragment>
);
}
export default function AppHeaderBanner() {
const showSurveyMessage = false;
const bannerMessage = showSurveyMessage ? getSurveyMessage() : getDefaultHiringMessage();
return FEATURE_TOGGLE.enable_website_banner ? (
<Typography
sx={[
{
fontWeight: 'medium',
},
(theme) => ({
color: '#fff',
p: '12px',
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
alignItems: { xs: 'start', sm: 'center' },
justifyContent: 'center',
fontSize: theme.typography.pxToRem(13),
background: `linear-gradient(-90deg, ${(theme.vars || theme).palette.primary[700]}, ${
(theme.vars || theme).palette.primary[500]
} 120%)`,
...theme.applyDarkStyles({
background: `linear-gradient(90deg, ${(theme.vars || theme).palette.primary[900]}, ${
(theme.vars || theme).palette.primary[600]
} 120%)`,
}),
}),
]}
>
{bannerMessage}
</Typography>
) : null;
}

View File

@@ -0,0 +1,55 @@
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { alpha } from '@mui/material/styles';
import { Link } from '@mui/docs/Link';
export default function TableOfContentsBanner() {
return (
<Link
href="https://war.ukraine.ua/support-ukraine/"
target="_blank"
sx={[
(theme) => ({
mt: 2,
mx: 0.5,
mb: 2,
p: 1,
pl: '10px',
display: 'flex',
alignItems: 'center',
gap: '10px',
border: '1px solid',
borderColor: (theme.vars || theme).palette.divider,
borderRadius: 1,
transitionProperty: 'all',
transitionTiming: 'cubic-bezier(0.4, 0, 0.2, 1)',
transitionDuration: '150ms',
'&:hover, &:focus-visible': {
backgroundColor: (theme.vars || theme).palette.primary[50],
borderColor: (theme.vars || theme).palette.primary[200],
},
}),
(theme) =>
theme.applyDarkStyles({
backgroundColor: alpha(theme.palette.primary[900], 0.2),
'&:hover, &:focus-visible': {
backgroundColor: alpha(theme.palette.primary[900], 0.4),
borderColor: (theme.vars || theme).palette.primary[900],
},
}),
]}
>
<Box sx={{ borderRadius: '3px', overflow: 'auto', width: 'fit-content', flexShrink: 0 }}>
<Box sx={{ height: 6, width: 16, backgroundColor: '#0057B7' }} />
<Box sx={{ height: 6, width: 16, backgroundColor: '#FFD700' }} />
</Box>
<Typography
component="span"
variant="caption"
sx={{ fontWeight: 'medium', color: 'text.secondary' }}
>
MUI stands in solidarity with Ukraine.
</Typography>
</Link>
);
}

View File

@@ -0,0 +1,140 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import useResizeHandle from 'docs/src/modules/utils/useResizeHandle';
import Frame from '../../action/Frame';
export default function CustomThemeComparison() {
const objectRef = React.useRef(null);
const handleDragging = React.useCallback((target, length) => {
const rect = target.getBoundingClientRect();
target.style.setProperty(
'--split-point',
`clamp(12px, ${((length * 100) / rect.width).toFixed(2)}%, calc(100% - 12px))`,
);
}, []);
const { dragging, getDragHandlers } = useResizeHandle(objectRef, {
onDragging: handleDragging,
});
return (
<Frame
ref={objectRef}
style={{ touchAction: dragging ? 'none' : 'auto' }}
sx={{
height: 'clamp(260px, 40vmax, 420px)',
mx: { md: '-64px' },
position: 'relative',
mb: 2,
'--split-point': '50%',
'& > *': {
borderRadius: '12px',
},
}}
>
<Frame.Demo
sx={{
overflow: 'auto',
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
clipPath: 'inset(0 calc(100% - var(--split-point)) 0 0)',
}}
>
<Box
component="img"
src="/static/screenshots/material-ui/getting-started/templates/dashboard-default.jpg"
sx={(theme) => ({
userSelect: 'none',
pointerEvents: 'none',
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'top',
...theme.applyStyles('dark', {
content:
'url(/static/screenshots/material-ui/getting-started/templates/dashboard-default-dark.jpg)',
}),
})}
/>
</Frame.Demo>
<Frame.Demo
sx={{
overflow: 'auto',
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
clipPath: 'inset(0 0 0 var(--split-point))',
}}
>
<Box
component="img"
src="/static/screenshots/material-ui/getting-started/templates/dashboard.jpg"
loading="lazy"
sx={(theme) => ({
userSelect: 'none',
pointerEvents: 'none',
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'top',
...theme.applyStyles('dark', {
content:
'url(/static/screenshots/material-ui/getting-started/templates/dashboard-dark.jpg)',
}),
})}
/>
</Frame.Demo>
<Box
{...getDragHandlers()}
sx={{
position: 'absolute',
top: 0,
bottom: 0,
width: 20,
left: 'var(--split-point)',
transform: 'translateX(-50%)',
cursor: 'col-resize',
}}
>
<Box
sx={{
margin: '0 auto',
width: 10,
bgcolor: 'background.default',
height: '100%',
borderInline: '1px solid',
borderColor: 'divider',
}}
/>
<Box
className="handleButton"
sx={{
position: 'absolute',
width: 2,
height: 14,
borderRadius: '12px',
bgcolor: 'primary.main',
top: '50%',
left: '50%',
transform: 'translate(-50%)',
transition: '0.15s',
}}
/>
</Box>
</Frame>
);
}

View File

@@ -0,0 +1,41 @@
import Box from '@mui/material/Box';
export default function FreeTemplatesBento() {
return (
<Box sx={{ position: 'relative' }}>
<Box
sx={(theme) => ({
width: '100vw',
position: 'relative',
left: '50%',
transform: 'translateX(-50%)',
py: 3,
borderBlock: '1px solid',
borderColor: 'divider',
background:
'linear-gradient(180deg, var(--muidocs-palette-primary-50) 0%, hsla(215, 15%, 97%, 0.6) 100%)',
...theme.applyStyles('dark', {
background:
'linear-gradient(180deg, hsla(210, 100%, 23%, 0.1) 0%, hsla(210, 14%, 4%, 0.5) 100%)',
}),
})}
>
<Box
component="img"
src="/static/blog/material-ui-v6-is-out/light-templates.png"
loading="lazy"
sx={(theme) => ({
width: '100%',
maxWidth: 1000,
mx: 'auto',
display: 'block',
height: 'auto',
...theme.applyStyles('dark', {
content: `url(/static/blog/material-ui-v6-is-out/dark-templates.png)`,
}),
})}
/>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,180 @@
import Box from '@mui/material/Box';
import { ThemeProvider, createTheme, styled } from '@mui/material/styles';
const defaultTheme = createTheme({
cssVariables: {
colorSchemeSelector: 'data-mui-color-scheme',
},
colorSchemes: { light: true, dark: true },
});
const traverseObject = (palette) => {
const result = {};
const traverse = (object, parts = []) => {
if (object && typeof object === 'object') {
for (const key of Object.keys(object)) {
traverse(object[key], [...parts, key]);
}
} else if (typeof object !== 'function') {
result[parts.join('.')] = object;
}
};
traverse(palette);
return result;
};
const Table = styled('table')(({ theme }) => ({
width: 'max-content', // to keep the content in 1 line
borderCollapse: 'separate',
borderSpacing: 0,
display: 'block',
height: 'clamp(30vmax, 40vmax, 40vmin)',
maxHeight: '40vmin',
overflowY: 'scroll',
'& th': {
textAlign: 'left',
padding: 10,
position: 'sticky',
top: 0,
zIndex: 1,
borderBottom: '1px solid',
borderColor: theme.palette.divider,
backgroundColor: theme.palette.grey[50],
...theme.applyStyles('dark', {
borderColor: 'var(--muidocs-palette-primaryDark-700)',
backgroundColor: 'hsl(210, 25%, 9%)',
}),
},
'& td': {
verticalAlign: 'top',
padding: '4px 10px',
fontSize: '0.75rem',
fontFamily: 'Menlo,Consolas,"Droid Sans Mono",monospace',
backgroundColor: theme.palette.common.white,
...theme.applyStyles('dark', {
backgroundColor: 'var(--muidocs-palette-primaryDark-900)',
}),
},
}));
const ColorSwatch = styled('span')(({ theme }) => ({
display: 'inline-block',
marginRight: '3px',
marginBottom: '1px',
verticalAlign: 'middle',
width: '0.75em',
height: '0.75em',
borderRadius: '2px',
border: '1px solid',
borderColor: theme.vars.palette.divider,
backgroundColor: 'currentcolor',
}));
export default function TemplateCarousel() {
const colors = traverseObject(defaultTheme.vars.palette);
const fonts = Object.keys(defaultTheme.vars.font).map(
(key) => `--mui-font-${key.replace('.', '-')}`,
);
const shadow = Object.keys(defaultTheme.vars.shadows).map((key) => `--mui-shadows-${key}`);
const overlay = Object.keys(defaultTheme.vars.overlays).map((key) => `--mui-overlays-${key}`);
const spacing = ['--mui-spacing'];
const shape = ['--mui-shape-borderRadius'];
const zIndex = Object.keys(defaultTheme.vars.zIndex).map((key) => `--mui-zIndex-${key}`);
return (
<ThemeProvider
theme={
// Use a function to ensure that the theme context does not inherit the upper branding theme.
// It's required because this demo needs to show default tokens of Material UI theme.
() => defaultTheme
}
>
<Box
sx={(theme) => ({
mb: 4,
border: '1px solid',
borderColor: 'divider',
borderRadius: '10px',
position: 'relative',
overflow: 'hidden',
...theme.applyStyles('dark', {
borderColor: 'var(--muidocs-palette-primaryDark-700)',
}),
mx: 'clamp(-147px, (1000px - 100vw) * 9999, 0px)',
})}
>
<Box
sx={(theme) => ({
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
height: 56,
pointerEvents: 'none',
opacity: 0.4,
background:
'linear-gradient(180deg, hsla(0, 0%, 100%, 0) 80%, hsla(215, 15%, 80%, 0.7) 100%)',
...theme.applyStyles('dark', {
opacity: 0.5,
background:
'linear-gradient(180deg, hsla(0, 0%, 0%, 0) 80%, hsla(215, 15%, 0%, 0.8) 100%)',
}),
})}
/>
<Box
sx={(theme) => ({
position: 'absolute',
top: 0,
right: 0,
height: '100%',
width: 56,
pointerEvents: 'none',
opacity: 0.4,
background:
'linear-gradient(90deg, hsla(0, 0%, 100%, 0) 80%, hsla(215, 15%, 80%, 0.7) 100%)',
...theme.applyStyles('dark', {
opacity: 0.5,
background:
'linear-gradient(90deg, hsla(0, 0%, 0%, 0) 80%, hsla(215, 15%, 0%, 0.8) 100%)',
}),
})}
/>
<Box sx={{ overflow: 'auto' }}>
<Table>
<thead>
<tr>
<th>Light colors</th>
<th>Dark colors</th>
<th>Font</th>
<th>Overlay</th>
<th>Shadow</th>
<th>Spacing</th>
<th>Shape</th>
<th>z Index</th>
</tr>
</thead>
<tbody>
{Object.entries(colors).map((color, index) => (
<tr key={index}>
<td>
<ColorSwatch data-mui-color-scheme="light" style={{ color: color[1] }} />
--mui-{color[0].replace('.', '-')}
</td>
<td>
<ColorSwatch data-mui-color-scheme="dark" style={{ color: color[1] }} />
--mui-{color[0].replace('.', '-')}
</td>
<td>{fonts[index]}</td>
<td>{overlay[index]}</td>
<td>{shadow[index]}</td>
<td>{spacing[index]}</td>
<td>{shape[index]}</td>
<td>{zIndex[index]}</td>
</tr>
))}
</tbody>
</Table>
</Box>
</Box>
</ThemeProvider>
);
}

View File

@@ -0,0 +1,201 @@
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import KeyboardArrowDownRounded from '@mui/icons-material/KeyboardArrowDownRounded';
import MuiAccordion from '@mui/material/Accordion';
import MuiAccordionSummary from '@mui/material/AccordionSummary';
import MuiAccordionDetail from '@mui/material/AccordionDetails';
import { MarkdownElement } from '@mui/docs/MarkdownElement';
import { createRender } from '@mui/internal-markdown';
import { Link } from '@mui/docs/Link';
import Section from 'docs/src/layouts/Section';
const Accordion = styled(MuiAccordion)(({ theme }) => ({
padding: theme.spacing(2),
transition: theme.transitions.create('box-shadow'),
borderRadius: theme.shape.borderRadius,
'&:hover': {
borderColor: theme.palette.primary[300],
boxShadow: `0px 4px 8px ${alpha(theme.palette.grey[200], 0.6)}`,
},
'&:not(:last-of-type)': {
marginBottom: theme.spacing(2),
},
'&::before': {
display: 'none',
},
'&::after': {
display: 'none',
},
...theme.applyDarkStyles({
'&:hover': {
borderColor: alpha(theme.palette.primary[600], 0.6),
boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.8)',
},
}),
}));
const AccordionSummary = styled(MuiAccordionSummary)(({ theme }) => ({
padding: theme.spacing(2),
margin: theme.spacing(-2),
minHeight: 'auto',
'&.Mui-expanded': {
minHeight: 'auto',
},
'& .MuiAccordionSummary-content': {
margin: 0,
paddingRight: theme.spacing(2),
'&.Mui-expanded': {
margin: 0,
},
},
}));
const AccordionDetails = styled(MuiAccordionDetail)(({ theme }) => ({
marginTop: theme.spacing(1),
padding: 0,
}));
// Data from https://www.notion.so/mui-org/Hiring-FAQ-64763b756ae44c37b47b081f98915501
const faqData = [
{
summary: 'Are there application deadlines?',
detail: 'No. If a job is visible on our careers page, then you can still apply.',
},
{
summary: 'Does MUI do whiteboarding during interviews?',
detail:
'No. We ask applicants to complete challenges that resemble the kinds of contributions we would expect from the role.',
},
{
summary: 'Would I be hired as an employee or contractor?',
detail: `
New team members can choose whether to join as an employee or a contractor. The legal requirements may vary significantly between countries, but we've designed our compensation packages to minimize the differences between each type of contract. You can also switch between the two statuses if and when your circumstances change.
For those who choose to join as employees:
- people in France are hired as full-time employees under the French [legal entity](https://www.infogreffe.fr/entreprise-societe/852357748-material-ui-750119B189960000.html).
- people outside of France are hired through an Employer of Record (EOR) such as [Deel](https://www.deel.com/).
`,
},
{
summary: 'Which countries does MUI hire from?',
detail: `
As a general rule, we can hire from any country where we can legally compensate you. This includes countries where:
- [we can send money via Wise](https://wise.com/help/articles/2571942/what-countries-can-i-send-to)
- [we can use Deel as an EOR](https://help.letsdeel.com/hc/en-gb/articles/4407737728273-Where-Is-Deel-Available)
Beyond that, we do have some limitations:
- We favor candidates working normal hours in the UTC-6 to UTC+5 timezone range. This isn't a hard requirement, but it greatly simplifies communication and collaboration.
- We can't hire fiscal residents of Russia due to legal and ethical constraints.
`,
},
{
summary: 'Does MUI offer visa sponsorship?',
detail: `At present, MUI does not provide visa sponsorship for individuals applying for new roles within the company. Candidates must already possess legal authorization to work in the country for which they're applying to be considered for employment.`,
},
{
summary: 'How would you describe the company culture?',
detail: `
We aim to build a team of people who:
- **seek accuracy over complacency.** People who enjoy discovering the truth more than being right. People who appreciate debate, embrace confusion, and reason carefully.
- **aim for clarity of thought.** People who take the time to understand the problem before solving it. People who can explain complex ideas in simple terms, and know how to tailor their communication to their audience.
- **possess self-determination.** People who are motivated to set their own goals and work towards them. People who can manage their own time and priorities, and don't wait to be told what to do.
- **view challenges as opportunities.** People who don't stop when things get tough—on the contrary, that's when the work starts to get really interesting.
See [company culture](https://www.notion.so/Company-culture-8c295a7b95564f2da03aca6759413391?pvs=21) in our handbook to learn more.
`,
},
{
summary: 'Can I use AI during the hiring process?',
detail: `
When testing candidates, we aim to simulate the actual conditions they would work in. You may use generative AI tools during the hiring process in the same way you might use them in your day-to-day work—for example, to speed up the process of writing boilerplate code.
However, we ask that you don't use AI to generate complete solutions to technical challenges, nor to replace your own decision-making. We need to see your thought process and problem-solving skills—not the output of a machine learning model.
`,
},
];
const render = createRender({
options: {
env: {
SOURCE_CODE_REPO: '',
},
},
});
function renderFAQItem(faq: (typeof faqData)[0]) {
return (
<Accordion variant="outlined">
<AccordionSummary
expandIcon={<KeyboardArrowDownRounded sx={{ fontSize: 20, color: 'primary.main' }} />}
>
<Typography variant="body2" component="span" sx={{ fontWeight: 'bold' }}>
{faq.summary}
</Typography>
</AccordionSummary>
<AccordionDetails sx={{ '& p:last-of-type': { mb: 0 } }}>
<MarkdownElement renderedMarkdown={render(faq.detail)} />
</AccordionDetails>
</Accordion>
);
}
export default function CareersFaq() {
return (
<Section bg="transparent" cozy>
<Typography id="faq" variant="h2" sx={{ mb: { xs: 2, sm: 4 } }}>
Frequently asked questions
</Typography>
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 6 }}>
{faqData.map((faq, index) => {
if (index % 2 !== 0) {
return null;
}
return renderFAQItem(faq);
})}
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
{faqData.map((faq, index) => {
if (index % 2 !== 1) {
return null;
}
return renderFAQItem(faq);
})}
<Paper
variant="outlined"
sx={(theme) => ({
p: 2,
borderStyle: 'dashed',
borderColor: 'divider',
bgcolor: 'white',
...theme.applyDarkStyles({
bgcolor: 'primaryDark.800',
}),
})}
>
<Box sx={{ textAlign: 'left' }}>
<Typography variant="body2" sx={{ color: 'text.primary', fontWeight: 'bold' }}>
Still have questions?
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary', my: 1, textAlign: 'left' }}>
Feel free to reach out with any other questions you might have about our hiring
process.
</Typography>
<Link href="mailto:job@mui.com" variant="body2">
Email us <KeyboardArrowRightRounded fontSize="small" />
</Link>
</Paper>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,179 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import { Link } from '@mui/docs/Link';
import IconImage from 'docs/src/components/icon/IconImage';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import ROUTES from 'docs/src/route';
const companyInfo = [
{
title: 'About us',
description: 'Learn about the team and our history.',
routeUrl: ROUTES.about,
},
{
title: 'Handbook',
description: 'Find out how we function as a company.',
routeUrl: ROUTES.handbook,
},
{
title: 'Blog',
description: 'Check out the latest product updates.',
routeUrl: ROUTES.blog,
},
];
interface CardContentBlockProps {
description: string;
title: string;
}
function CardContentBlock({ title, description }: CardContentBlockProps) {
return (
<React.Fragment>
<Typography component="h2" variant="body2" sx={{ fontWeight: 'semiBold' }}>
{title}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>
{description}
</Typography>
<Typography variant="body2" color="primary" sx={{ fontWeight: 'bold', mt: 'auto' }}>
Read more <KeyboardArrowRightRounded fontSize="small" sx={{ verticalAlign: 'middle' }} />
</Typography>
</React.Fragment>
);
}
function RemoteAwardCard() {
return (
<Paper
component={Link}
href="/blog/remote-award-win-2024/"
noLinkStyle
variant="outlined"
sx={{ p: 2, display: 'flex', flexDirection: 'column ' }}
>
<Box
sx={{
mb: 2,
maxWidth: { xs: 315, sm: 325 },
maxHeight: 315,
display: 'flex',
aspectRatio: '1 / 1',
border: '1px solid',
borderColor: 'divider',
borderRadius: '6px',
overflow: 'clip',
}}
>
<Box
component="img"
src="/static/branding/careers/remote-award-light.png"
alt="MUI is the winner of the Remote Excellence Awards in the Small and Mighty for SMEs category."
sx={[
{
height: '1200px',
width: '1200px',
},
(theme) => ({
width: '100%',
height: '100%',
...theme.applyDarkStyles({
content: `url(/static/branding/careers/remote-award-dark.png)`,
}),
}),
]}
/>
</Box>
<Box sx={{ mt: 'auto' }}>
<CardContentBlock
title="Remote Excellence Awards"
description={`Winners in the first-ever Remote Excellence Awards, in the Small & Mighty category! 🎉`}
/>
</Box>
</Paper>
);
}
export default function PerksBenefits() {
return (
<Section bg="gradient" cozy>
<Grid container spacing={5} alignItems="center">
<Grid size={{ md: 6 }}>
<SectionHeadline
overline="Working at MUI"
title={
<Typography variant="h2" id="perks-and-benefits">
Perks & benefits
</Typography>
}
description="To help you go above and beyond with us, we provide:"
/>
<Box sx={{ maxWidth: 500 }}>
{[
['100% remote work', 'Our entire company is globally distributed.'],
['Time off', 'We provide 33 days of paid time off globally.'],
[
'Retreats',
'We meet up every 8+ months for a week of working & having fun together!',
],
[
'Equipment',
'We let you choose the hardware of your choice (within a given budget).',
],
].map((textArray) => (
<Box
key={textArray[0]}
sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 1, py: 0.5 }}
>
<IconImage name="pricing/yes" />
<div>
<Typography
variant="body2"
sx={{ color: 'text.primary', fontWeight: 'semiBold' }}
>
{textArray[0]}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{textArray[1]}
</Typography>
</div>
</Box>
))}
</Box>
</Grid>
<Grid
sx={{
p: { xs: 2, sm: 0 },
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 2,
}}
size={{ xs: 12, md: 6 }}
>
<RemoteAwardCard />
<Stack spacing={2} useFlexGap>
{companyInfo.map(({ title, description, routeUrl }) => (
<Paper
key={title}
component={Link}
href={routeUrl}
noLinkStyle
variant="outlined"
sx={{ p: 2, width: '100%', flexGrow: 1, display: 'flex', flexDirection: 'column' }}
>
<CardContentBlock title={title} description={description} />
</Paper>
))}
</Stack>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,61 @@
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
interface RoleProps {
description: string;
title: string;
url?: string;
}
export default function RoleEntry(props: RoleProps) {
if (props.url) {
return (
<Box
sx={{
py: 1,
display: 'flex',
flexDirection: { xs: 'column', lg: 'row' },
justifyContent: 'space-between',
alignItems: 'start',
gap: 2,
}}
>
<div>
<Typography
variant="body1"
gutterBottom
sx={{ color: 'text.primary', fontWeight: 'medium' }}
>
{props.title}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', maxWidth: 550 }}>
{props.description}
</Typography>
</div>
<Button
component="a"
variant="outlined"
color="secondary"
size="small"
href={props.url}
endIcon={<KeyboardArrowRightRounded />}
>
More about this role
</Button>
</Box>
);
}
return (
<div>
<Typography variant="body1" gutterBottom sx={{ color: 'text.primary', fontWeight: 'medium' }}>
{props.title}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', maxWidth: 650 }}>
{props.description}
</Typography>
</div>
);
}

View File

@@ -0,0 +1,169 @@
import * as React from 'react';
import { SxProps } from '@mui/system';
import { Theme, styled, alpha } from '@mui/material/styles';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import FormLabel from '@mui/material/FormLabel';
import FormHelperText from '@mui/material/FormHelperText';
import InputBase, { inputBaseClasses } from '@mui/material/InputBase';
import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded';
const NEWSLETTER_SUBSCRIBE_URL =
process.env.DEPLOY_ENV === 'production' || process.env.DEPLOY_ENV === 'staging'
? 'https://f0433e60.sibforms.com/serve/MUIEAHEhgYhMvLAw0tycwk1BQaIB-q0akob3JdtDBmHLhSR-jLheJ2T44LFCz27alz9wq_Nkdz9EK7Y8hzM1vQND9kTFyKkkhTIbEzXaH5d-_S9Fw4PXS1zAK8efPY6nhCdoAop1SKTeZ_GAPW5S0xBFQRLUGYbvvRgE4Q2Ki_f1KjbiCqaRuzmj_I3SD1r0CoR4INmK3CLtF4kF'
: 'https://f0433e60.sibforms.com/serve/MUIEAE9LexIU5u5hYkoDJ-Mc379-irLHNIlGEgCm5njkAwg6OYFfNQTd25n4SO6vJom9WvQ89GJ0sYBzFYswLRewcOvD_dRtoFycXIObP8SMm-kNO1CdXKaWEZutrfqMKygHb1Je1QBGrMUnJg8J5qVeCwa7rSPBN0A1_6Ug3SiGjgIlbiCcMVA4KbhaYTiBvKkaejlCjgZcLHBT';
const Form = styled('form')({});
function searchParams(params: any) {
return Object.keys(params)
.filter((key) => params[key] != null)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
}
export default function EmailSubscribe({ sx }: { sx?: SxProps<Theme> }) {
const [form, setForm] = React.useState<{
email: string;
status: 'initial' | 'loading' | 'failure' | 'sent';
}>({
email: '',
status: 'initial',
});
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setForm((current) => ({ ...current, status: 'loading' }));
try {
await fetch(NEWSLETTER_SUBSCRIBE_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
mode: 'no-cors',
body: searchParams({
EMAIL: form.email,
email_address_check: '',
locale: 'en',
}),
});
setForm((current) => ({ ...current, status: 'sent' }));
} catch (error) {
setForm((current) => ({ ...current, status: 'failure' }));
}
};
if (form.status === 'sent') {
return (
<Alert
icon={<CheckCircleRoundedIcon fontSize="small" />}
severity="success"
sx={[
(theme) => ({
fontWeight: 'medium',
bgcolor: 'success.50',
border: '1px solid',
borderColor: 'success.200',
color: 'success.900',
...theme.applyDarkStyles({
color: 'success.200',
bgcolor: alpha(theme.palette.success[700], 0.1),
borderColor: alpha(theme.palette.success[600], 0.3),
}),
}),
]}
>
Go to your email and open the <strong>confirmation email</strong> to confirm your
subscription.
</Alert>
);
}
return (
<Form onSubmit={handleSubmit} sx={sx}>
<FormLabel
htmlFor="email-subscribe"
sx={{ typography: 'caption', color: 'text.secondary', fontWeight: 'medium' }}
>
Your email
</FormLabel>
<Box
sx={{
mt: 1,
width: { xs: '100%', sm: 'auto' },
maxWidth: { xs: '100%', sm: 320 },
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 1,
}}
>
<InputBase
id="email-subscribe"
name="email"
type="email"
placeholder="example@email.com"
autoComplete="email"
value={form.email}
onChange={(event) => setForm({ email: event.target.value, status: 'initial' })}
inputProps={{ required: true }}
sx={[
(theme) => ({
typography: 'body1',
flexGrow: 1,
minWidth: 220,
borderRadius: '10px',
border: '1px solid',
borderColor: 'grey.300',
bgcolor: '#FFF',
boxShadow: `inset 0 1px 2px ${
(theme.vars || theme).palette.grey[50]
}, 0 2px .5px ${alpha(theme.palette.grey[100], 0.5)}`,
'&:hover': {
borderColor: 'grey.400',
boxShadow: `inset 0 1px 2px ${(theme.vars || theme).palette.grey[100]}`,
},
[`&.${inputBaseClasses.focused}`]: {
boxShadow: `0 0 0 3px ${alpha(theme.palette.primary[500], 0.5)}`,
borderColor: 'primary.500',
},
[`& .${inputBaseClasses.input}`]: {
borderRadius: theme.shape.borderRadius,
px: 1,
},
}),
(theme) =>
theme.applyDarkStyles({
bgcolor: 'primaryDark.800',
borderColor: alpha(theme.palette.primaryDark[600], 0.8),
boxShadow: `inset 0 1px 1px ${
(theme.vars || theme).palette.primaryDark[900]
}, 0 2px .5px ${(theme.vars || theme).palette.common.black}`,
'&:hover': {
borderColor: 'primaryDark.500',
boxShadow: `inset 0 1px 2px ${(theme.vars || theme).palette.common.black}`,
},
[`&.${inputBaseClasses.focused}`]: {
borderColor: 'primary.400',
},
}),
]}
/>
<Button variant="outlined" size="small" disabled={form.status === 'loading'} type="submit">
Subscribe
</Button>
</Box>
{form.status === 'failure' && (
<FormHelperText
sx={(theme) => ({
mt: 1,
fontWeight: 'semiBold',
color: 'error.700',
...theme.applyDarkStyles({
color: 'error.400',
}),
})}
>
Oops! Something went wrong, please try again later.
</FormHelperText>
)}
</Form>
);
}

View File

@@ -0,0 +1,389 @@
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import ButtonBase from '@mui/material/ButtonBase';
import Popper from '@mui/material/Popper';
import Paper from '@mui/material/Paper';
import { unstable_debounce as debounce } from '@mui/utils';
import Fade from '@mui/material/Fade';
import Typography from '@mui/material/Typography';
import IconImage from 'docs/src/components/icon/IconImage';
import ROUTES from 'docs/src/route';
import { Link } from '@mui/docs/Link';
import MuiProductSelector from 'docs/src/modules/components/MuiProductSelector';
const Navigation = styled('nav')(({ theme }) => [
{
'& > div': {
cursor: 'default',
},
'& ul': {
padding: 0,
margin: 0,
listStyle: 'none',
display: 'flex',
},
'& li': {
...theme.typography.body2,
color: (theme.vars || theme).palette.text.secondary,
fontWeight: theme.typography.fontWeightSemiBold,
'& > a, & > button': {
display: 'inline-block',
color: 'inherit',
font: 'inherit',
textDecoration: 'none',
padding: theme.spacing('6px', '8px'),
borderRadius: (theme.vars || theme).shape.borderRadius,
border: '1px solid transparent',
'&:hover': {
color: (theme.vars || theme).palette.text.primary,
backgroundColor: (theme.vars || theme).palette.grey[50],
borderColor: (theme.vars || theme).palette.grey[100],
'@media (hover: none)': {
backgroundColor: 'initial',
// Reset on touch devices, it doesn't add specificity
},
},
'&:focus-visible': {
outline: `3px solid ${alpha(theme.palette.primary[500], 0.5)}`,
outlineOffset: '2px',
},
},
},
},
theme.applyDarkStyles({
'& li': {
'& > a, & > button': {
'&:hover': {
color: (theme.vars || theme).palette.primary[50],
backgroundColor: alpha(theme.palette.primaryDark[700], 0.8),
borderColor: (theme.vars || theme).palette.divider,
},
},
},
}),
]);
const PRODUCT_IDS = [
'product-core',
'product-advanced',
'product-templates',
'product-design',
] as const;
type ProductSubMenuProps = {
icon: React.ReactElement<unknown>;
name: React.ReactNode;
description: React.ReactNode;
chip?: React.ReactNode;
href: string;
} & Omit<React.JSX.IntrinsicElements['a'], 'ref'>;
const ProductSubMenu = React.forwardRef<HTMLAnchorElement, ProductSubMenuProps>(
function ProductSubMenu({ icon, name, description, chip, href, ...props }, ref) {
return (
<Box
component={Link}
href={href}
ref={ref}
sx={(theme) => ({
display: 'flex',
alignItems: 'center',
py: 2,
pr: 3,
'&:hover, &:focus': {
backgroundColor: (theme.vars || theme).palette.grey[50],
outline: 0,
'@media (hover: none)': {
backgroundColor: 'initial',
outline: 'initial',
},
},
...theme.applyDarkStyles({
'&:hover, &:focus': {
backgroundColor: alpha(theme.palette.primaryDark[700], 0.4),
},
}),
})}
{...props}
>
<Box sx={{ px: 2 }}>{icon}</Box>
<div style={{ flexGrow: 1 }}>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: '0.5rem',
}}
>
<Typography variant="body2" sx={{ color: 'text.primary', fontWeight: 'bold' }}>
{name}
</Typography>
{chip}
</div>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{description}
</Typography>
</div>
</Box>
);
},
);
export default function HeaderNavBar() {
const [subMenuOpen, setSubMenuOpen] = React.useState<null | 'products' | 'docs'>(null);
const [subMenuIndex, setSubMenuIndex] = React.useState<number | null>(null);
const navRef = React.useRef<HTMLUListElement>(null);
const productSelectorRef = React.useRef<HTMLDivElement>(null);
const productsMenuRef = React.useRef<HTMLButtonElement>(null);
const docsMenuRef = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
if (typeof subMenuIndex === 'number' && subMenuOpen === 'products') {
document.getElementById(PRODUCT_IDS[subMenuIndex])!.focus();
}
if (typeof subMenuIndex === 'number' && subMenuOpen === 'docs') {
(productSelectorRef.current!.querySelector('[role="menuitem"]') as HTMLElement).focus();
}
}, [subMenuIndex, subMenuOpen]);
function handleKeyDown(event: React.KeyboardEvent) {
let menuItem;
if (subMenuOpen === 'products') {
menuItem = productsMenuRef.current!;
} else if (subMenuOpen === 'docs') {
menuItem = docsMenuRef.current!;
} else {
return;
}
if (event.key === 'ArrowDown') {
event.preventDefault();
if (subMenuOpen === 'products') {
setSubMenuIndex((prevValue) => {
if (prevValue === null) {
return 0;
}
if (prevValue === PRODUCT_IDS.length - 1) {
return 0;
}
return prevValue + 1;
});
} else if (subMenuOpen === 'docs') {
setSubMenuIndex(0);
}
} else if (event.key === 'ArrowUp') {
event.preventDefault();
if (subMenuOpen === 'products') {
setSubMenuIndex((prevValue) => {
if (prevValue === null) {
return 0;
}
if (prevValue === 0) {
return PRODUCT_IDS.length - 1;
}
return prevValue - 1;
});
} else if (subMenuOpen === 'docs') {
setSubMenuIndex(0);
}
} else if (event.key === 'Escape' || event.key === 'Tab') {
menuItem.focus();
setSubMenuOpen(null);
setSubMenuIndex(null);
}
}
const setSubMenuOpenDebounced = React.useMemo(
() => debounce(setSubMenuOpen, 40),
[setSubMenuOpen],
);
const setSubMenuOpenUndebounce =
(value: typeof subMenuOpen) => (event: React.MouseEvent | React.FocusEvent) => {
setSubMenuOpenDebounced.clear();
setSubMenuOpen(value);
if (event.type === 'mouseenter') {
// Reset keyboard
setSubMenuIndex(null);
}
};
const handleClickMenu = (value: typeof subMenuOpen) => () => {
setSubMenuOpenDebounced.clear();
setSubMenuOpen(subMenuOpen ? null : value);
};
React.useEffect(() => {
return () => {
setSubMenuOpenDebounced.clear();
};
}, [setSubMenuOpenDebounced]);
return (
<Navigation>
<ul ref={navRef} onKeyDown={handleKeyDown}>
<li
onMouseEnter={setSubMenuOpenUndebounce('products')}
onFocus={setSubMenuOpenUndebounce('products')}
onMouseLeave={() => setSubMenuOpenDebounced(null)}
onBlur={setSubMenuOpenUndebounce(null)}
>
<ButtonBase
ref={productsMenuRef}
aria-haspopup
aria-expanded={subMenuOpen === 'products' ? 'true' : 'false'}
onClick={handleClickMenu('products')}
aria-controls={subMenuOpen === 'products' ? 'products-popper' : undefined}
>
Products
</ButtonBase>
<Popper
id="products-popper"
open={subMenuOpen === 'products'}
anchorEl={productsMenuRef.current}
transition
placement="bottom-start"
style={{
zIndex: 1200,
pointerEvents: subMenuOpen === 'products' ? undefined : 'none',
}}
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={250}>
<Paper
variant="outlined"
sx={(theme) => ({
mt: 1,
minWidth: 498,
overflow: 'hidden',
borderColor: 'grey.200',
bgcolor: 'background.paper',
boxShadow: `0px 4px 16px ${alpha(theme.palette.grey[200], 0.8)}`,
'& ul': {
margin: 0,
padding: 0,
listStyle: 'none',
},
'& li:not(:last-of-type)': {
borderBottom: '1px solid',
borderColor: 'grey.100',
},
'& a': { textDecoration: 'none' },
...theme.applyDarkStyles({
borderColor: 'primaryDark.700',
bgcolor: 'primaryDark.900',
boxShadow: `0px 4px 16px ${alpha(theme.palette.common.black, 0.8)}`,
'& li:not(:last-of-type)': {
borderColor: 'primaryDark.700',
},
}),
})}
>
<ul>
<li>
<ProductSubMenu
id={PRODUCT_IDS[0]}
href={ROUTES.productCore}
icon={<IconImage name="product-core" />}
name="MUI Core"
description="Ready-to-use foundational React components, free forever."
/>
</li>
<li>
<ProductSubMenu
id={PRODUCT_IDS[1]}
href={ROUTES.productAdvanced}
icon={<IconImage name="product-advanced" />}
name="MUI X"
description="Advanced and powerful components for complex use cases."
/>
</li>
<li>
<ProductSubMenu
id={PRODUCT_IDS[2]}
href={ROUTES.productTemplates}
icon={<IconImage name="product-templates" />}
name="Templates"
description="Fully built templates for your application."
/>
</li>
<li>
<ProductSubMenu
id={PRODUCT_IDS[3]}
href={ROUTES.productDesignKits}
icon={<IconImage name="product-designkits" />}
name="Design Kits"
description="Material UI components in your favorite design tool."
/>
</li>
</ul>
</Paper>
</Fade>
)}
</Popper>
</li>
<li
onMouseEnter={setSubMenuOpenUndebounce('docs')}
onFocus={setSubMenuOpenUndebounce('docs')}
onMouseLeave={() => setSubMenuOpenDebounced(null)}
onBlur={setSubMenuOpenUndebounce(null)}
>
<ButtonBase
ref={docsMenuRef}
aria-haspopup
aria-expanded={subMenuOpen === 'docs' ? 'true' : 'false'}
onClick={handleClickMenu('docs')}
aria-controls={subMenuOpen === 'docs' ? 'docs-popper' : undefined}
>
Docs
</ButtonBase>
<Popper
id="docs-popper"
open={subMenuOpen === 'docs'}
anchorEl={docsMenuRef.current}
transition
placement="bottom-start"
style={{ zIndex: 1200, pointerEvents: subMenuOpen === 'docs' ? undefined : 'none' }}
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={250}>
<Paper
variant="outlined"
sx={(theme) => ({
mt: 1,
overflow: 'hidden',
borderColor: 'grey.200',
bgcolor: 'background.paper',
boxShadow: `0px 4px 16px ${alpha(theme.palette.grey[200], 0.8)}`,
...theme.applyDarkStyles({
borderColor: 'primaryDark.700',
bgcolor: 'primaryDark.900',
boxShadow: `0px 4px 16px ${alpha(theme.palette.common.black, 0.8)}`,
}),
})}
>
<MuiProductSelector ref={productSelectorRef} />
</Paper>
</Fade>
)}
</Popper>
</li>
<li>
<Link href={ROUTES.pricing}>Pricing</Link>
</li>
<li>
<Link href={ROUTES.about}>About us</Link>
</li>
<li>
<Link href={ROUTES.blog}>Blog</Link>
</li>
</ul>
</Navigation>
);
}

View File

@@ -0,0 +1,287 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Collapse from '@mui/material/Collapse';
import { ClickAwayListener } from '@mui/base/ClickAwayListener';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import KeyboardArrowDownRounded from '@mui/icons-material/KeyboardArrowDownRounded';
import SvgHamburgerMenu from 'docs/src/icons/SvgHamburgerMenu';
import { Link } from '@mui/docs/Link';
import ROUTES from 'docs/src/route';
const Anchor = styled('a')<{ component?: React.ElementType; noLinkStyle?: boolean }>(
({ theme }) => [
{
...theme.typography.body2,
fontWeight: theme.typography.fontWeightBold,
textDecoration: 'none',
border: 'none',
width: '100%',
backgroundColor: 'transparent',
color: (theme.vars || theme).palette.text.secondary,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
padding: theme.spacing(1),
borderRadius: theme.spacing(1),
transition: theme.transitions.create('background'),
'&:hover, &:focus-visible': {
backgroundColor: (theme.vars || theme).palette.grey[100],
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
},
},
},
theme.applyDarkStyles({
color: '#fff',
'&:hover, &:focus-visible': {
backgroundColor: (theme.vars || theme).palette.primaryDark[700],
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
},
},
}),
],
);
const UList = styled('ul')({
listStyleType: 'none',
padding: 0,
margin: 0,
});
const PRODUCTS = [
{
name: 'MUI Core',
description: 'Ready-to-use foundational React components, free forever.',
href: ROUTES.productCore,
},
{
name: 'MUI X',
description: 'Advanced and powerful components for complex use cases.',
href: ROUTES.productAdvanced,
},
{
name: 'Templates',
description: 'Fully built templates for your application.',
href: ROUTES.productTemplates,
},
{
name: 'Design Kits',
description: 'Material UI components in your favorite design tool.',
href: ROUTES.productDesignKits,
},
];
const DOCS = [
{
name: 'Material UI',
description: "Component library that implements Google's Material Design.",
href: ROUTES.materialDocs,
},
{
name: 'Joy UI',
description: "Component library that implements MUI's own in-house design principles.",
href: ROUTES.joyDocs,
},
{
name: 'MUI Base',
description: 'Unstyled React components and low-level hooks.',
href: ROUTES.baseDocs,
},
{
name: 'MUI System',
description: 'CSS utilities for rapidly laying out custom designs.',
href: ROUTES.systemDocs,
},
{
name: 'MUI X',
description: 'Advanced components for complex use cases.',
href: ROUTES.xIntro,
},
];
export default function HeaderNavDropdown() {
const [open, setOpen] = React.useState(false);
const [productsOpen, setProductsOpen] = React.useState(true);
const [docsOpen, setDocsOpen] = React.useState(false);
const hambugerRef = React.useRef<HTMLButtonElement>(null);
return (
<React.Fragment>
<IconButton
color="primary"
aria-label="Menu"
ref={hambugerRef}
disableRipple
onClick={() => setOpen((value) => !value)}
sx={{
position: 'relative',
'& rect': {
transformOrigin: 'center',
transition: '0.2s',
},
...(open && {
'& rect:first-of-type': {
transform: 'translate(1.5px, 1.6px) rotateZ(-45deg)',
},
'& rect:last-of-type': {
transform: 'translate(1.5px, -1.2px) rotateZ(45deg)',
},
}),
}}
>
<SvgHamburgerMenu />
</IconButton>
<ClickAwayListener
onClickAway={(event) => {
if (!hambugerRef.current!.contains(event.target as Node)) {
setOpen(false);
}
}}
>
<Collapse
in={open}
sx={(theme) => ({
position: 'fixed',
top: 56,
left: 0,
right: 0,
boxShadow: `0px 16px 20px rgba(170, 180, 190, 0.3)`,
...theme.applyDarkStyles({
boxShadow: '0px 16px 20px rgba(0, 0, 0, 0.8)',
}),
})}
>
<Box
sx={{
p: 2,
bgcolor: 'background.default',
maxHeight: 'calc(100vh - 56px)',
overflow: 'auto',
}}
>
<UList
sx={(theme) => ({
'& ul': {
borderLeft: '1px solid',
borderColor: 'grey.100',
...theme.applyDarkStyles({
borderColor: 'primaryDark.700',
}),
pl: 1,
pb: 1,
ml: 1,
},
})}
>
<li>
<Anchor
as="button"
onClick={() => setProductsOpen((bool) => !bool)}
sx={{ justifyContent: 'space-between' }}
>
Products
<KeyboardArrowDownRounded
color="primary"
sx={{
transition: '0.3s',
transform: productsOpen ? 'rotate(-180deg)' : 'rotate(0)',
}}
/>
</Anchor>
<Collapse in={productsOpen}>
<UList>
{PRODUCTS.map((item) => (
<li key={item.name}>
<Anchor
href={item.href}
as={Link}
noLinkStyle
sx={{ flexDirection: 'column', alignItems: 'initial' }}
>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
{item.name}
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{item.description}
</Typography>
</Anchor>
</li>
))}
</UList>
</Collapse>
</li>
<li>
<Anchor
as="button"
onClick={() => setDocsOpen((bool) => !bool)}
sx={{ justifyContent: 'space-between' }}
>
Docs
<KeyboardArrowDownRounded
color="primary"
sx={{
transition: '0.3s',
transform: docsOpen ? 'rotate(-180deg)' : 'rotate(0)',
}}
/>
</Anchor>
<Collapse in={docsOpen}>
<UList>
{DOCS.map((item) => (
<li key={item.name}>
<Anchor
href={item.href}
as={Link}
noLinkStyle
sx={{ flexDirection: 'column', alignItems: 'initial' }}
>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
{item.name}
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{item.description}
</Typography>
</Anchor>
</li>
))}
</UList>
</Collapse>
</li>
<li>
<Anchor href={ROUTES.pricing} as={Link} noLinkStyle>
Pricing
</Anchor>
</li>
<li>
<Anchor href={ROUTES.about} as={Link} noLinkStyle>
About us
</Anchor>
</li>
<li>
<Anchor href={ROUTES.blog} as={Link} noLinkStyle>
Blog
</Anchor>
</li>
</UList>
</Box>
</Collapse>
</ClickAwayListener>
</React.Fragment>
);
}

View File

@@ -0,0 +1,71 @@
import { useColorScheme, useTheme } from '@mui/material/styles';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined';
import LightModeOutlined from '@mui/icons-material/LightModeOutlined';
import { useColorSchemeShim } from 'docs/src/modules/components/ThemeContext';
function CssVarsModeToggle(props: { onChange: (newMode: string) => void }) {
const { mode, systemMode, setMode } = useColorScheme();
const calculatedMode = mode === 'system' ? systemMode : mode;
return (
<Tooltip title={calculatedMode === 'dark' ? 'Turn on the light' : 'Turn off the light'}>
<IconButton
color="primary"
size="small"
disableTouchRipple
disabled={!calculatedMode}
onClick={() => {
const newMode = calculatedMode === 'dark' ? 'light' : 'dark';
props.onChange(newMode);
setMode(newMode);
}}
>
{!calculatedMode
? null
: {
light: <DarkModeOutlined fontSize="small" />,
dark: <LightModeOutlined fontSize="small" />,
}[calculatedMode]}
</IconButton>
</Tooltip>
);
}
export default function ThemeModeToggle() {
// TODO replace with useColorScheme once all pages support css vars
const { mode, systemMode, setMode } = useColorSchemeShim();
const calculatedMode = mode === 'system' ? systemMode : mode;
const theme = useTheme();
// Server-side hydration
if (mode === null) {
return <IconButton color="primary" size="small" disableTouchRipple />;
}
// TODO remove this code branch, all pages should be migrated to use CssVarsProvider
if (!theme.vars) {
return (
<Tooltip title={calculatedMode === 'dark' ? 'Turn on the light' : 'Turn off the light'}>
<IconButton
color="primary"
size="small"
disableTouchRipple
onClick={() => {
setMode(calculatedMode === 'dark' ? 'light' : 'dark');
}}
>
{calculatedMode === 'dark' ? (
<LightModeOutlined fontSize="small" />
) : (
<DarkModeOutlined fontSize="small" />
)}
</IconButton>
</Tooltip>
);
}
return <CssVarsModeToggle onChange={setMode} />;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
import { Link } from '@mui/docs/Link';
import OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded';
export default function BacklinkSponsor(props: {
item: {
name: string;
description: string;
href: string;
};
}) {
const { item } = props;
// Keep it under two rows maximum.
if (item.description.length > 50) {
throw new Error(
`${item.name}'s description is too long (${item.description.length} characters). It must fit into two line, so under 50 characters.`,
);
}
return (
<Link
data-ga-event-category="sponsor"
data-ga-event-action="homepage"
data-ga-event-label={new URL(item.href).hostname}
href={item.href}
title={item.description}
target="_blank"
rel="sponsored noopener"
sx={{ mr: { xs: 1, md: 2 }, mt: 1.5, fontSize: { xs: 13, md: 14 } }}
>
{item.name}
<OpenInNewRoundedIcon sx={{ fontSize: 14 }} />
</Link>
);
}

View File

@@ -0,0 +1,204 @@
import Grid from '@mui/material/Grid';
import IconImage, { IconImageProps } from 'docs/src/components/icon/IconImage';
export const CORE_CUSTOMERS: Array<IconImageProps> = [
{
alt: 'Spotify logo',
name: 'companies/spotify',
width: 100,
height: 52,
},
{
alt: 'Amazon logo',
name: 'companies/amazon',
width: 80,
height: 52,
},
{
alt: 'Nasa logo',
name: 'companies/nasa',
mode: '',
width: 52,
height: 42,
},
{
alt: 'Netflix logo',
name: 'companies/netflix',
mode: '',
width: 80,
height: 52,
},
{
alt: 'Unity logo',
name: 'companies/unity',
width: 69,
height: 52,
},
{
alt: 'Shutterstock logo',
name: 'companies/shutterstock',
width: 100,
height: 52,
},
];
export const ADVANCED_CUSTOMERS: Array<IconImageProps> = [
{
alt: 'Southwest logo',
name: 'companies/southwest',
width: 130,
height: 54,
style: {
marginTop: -10,
},
},
{
alt: 'Tesla logo',
name: 'companies/tesla',
width: 140,
height: 52,
style: {
marginTop: -11,
},
},
{
alt: 'Apple logo',
name: 'companies/apple',
width: 29,
height: 52,
style: {
marginTop: -21,
},
},
{
alt: 'Siemens logo',
name: 'companies/siemens',
mode: '',
width: 119,
height: 59,
style: {
marginTop: -13,
},
},
{
alt: 'Volvo logo',
name: 'companies/volvo',
width: 128,
height: 52,
style: {
marginTop: -11,
},
},
{
alt: 'Deloitte logo',
name: 'companies/deloitte',
width: 97,
height: 52,
style: {
marginTop: -12,
},
},
];
export const DESIGNKITS_CUSTOMERS: Array<IconImageProps> = [
{
alt: 'Spotify logo',
name: 'companies/spotify',
width: 100,
height: 52,
},
{
alt: 'Amazon logo',
name: 'companies/amazon',
width: 80,
height: 52,
},
{
alt: 'Apple logo',
name: 'companies/apple',
width: 29,
height: 52,
},
{
alt: 'Netflix logo',
name: 'companies/netflix',
mode: '',
width: 80,
height: 52,
},
{
alt: 'X logo',
name: 'companies/x',
mode: '',
width: 30,
height: 30,
},
{
alt: 'Salesforce logo',
name: 'companies/salesforce',
mode: '',
width: 50,
height: 52,
},
];
export const TEMPLATES_CUSTOMERS: Array<IconImageProps> = [
{
alt: 'Ebay logo',
name: 'companies/ebay',
width: 73,
height: 52,
},
{
alt: 'Amazon logo',
name: 'companies/amazon',
width: 80,
height: 52,
},
{
alt: 'Samsung logo',
name: 'companies/samsung',
mode: '',
width: 88,
height: 52,
},
{
alt: 'Patreon logo',
name: 'companies/patreon',
width: 103,
height: 52,
},
{
alt: 'AT&T logo',
name: 'companies/atandt',
width: 71,
height: 52,
},
{
alt: 'Verizon logo',
name: 'companies/verizon',
width: 91,
height: 52,
},
];
export default function CompaniesGrid({ data }: { data: Array<IconImageProps> }) {
return (
<Grid container spacing={4}>
{data.map((imgProps) => (
<Grid
key={imgProps.name}
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
objectFit: 'contain',
}}
size={{ xs: 6, sm: 4, md: 2 }}
>
<IconImage alt={imgProps.alt} loading="eager" {...imgProps} />
</Grid>
))}
</Grid>
);
}

View File

@@ -0,0 +1,144 @@
import * as React from 'react';
import { ThemeProvider, createTheme, useTheme, alpha } from '@mui/material/styles';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import MaterialDesignDemo, { componentCode } from 'docs/src/components/home/MaterialDesignDemo';
import ShowcaseContainer, { ShowcaseCodeWrapper } from 'docs/src/components/home/ShowcaseContainer';
import PointerContainer, { Data } from 'docs/src/components/home/ElementPointer';
import MoreInfoBox from 'docs/src/components/action/MoreInfoBox';
import MaterialVsCustomToggle from 'docs/src/components/action/MaterialVsCustomToggle';
import FlashCode from 'docs/src/components/animation/FlashCode';
import ROUTES from 'docs/src/route';
const lineMapping: Record<string, number | number[]> = {
card: [0, 20],
cardmedia: [1, 5],
stack: [6, 19],
stack2: [7, 16],
typography: 8,
stack3: [9, 16],
chip: [10, 14],
rating: 15,
switch: 18,
};
export default function CoreShowcase() {
const { vars, ...globalTheme } = useTheme();
const mode = globalTheme.palette.mode;
const [element, setElement] = React.useState<Data>({ id: null, name: null, target: null });
const [customized, setCustomized] = React.useState(true);
const theme = React.useMemo(
() =>
customized
? createTheme(globalTheme, {
palette: {
background: {
default:
mode === 'dark'
? globalTheme.palette.primaryDark[900]
: globalTheme.palette.grey[50],
},
},
shape: {
borderRadius: 12,
},
shadows: ['none', '0px 4px 20px 0px hsla(210, 14%, 28%, 0.2)'],
components: {
MuiCard: {
styleOverrides: {
root: {
boxShadow:
mode === 'dark'
? `0 4px 8px ${alpha(globalTheme.palette.common.black, 0.3)}`
: `0 4px 8px ${alpha(globalTheme.palette.primaryDark[300], 0.3)}`,
backgroundColor:
mode === 'dark' ? globalTheme.palette.primaryDark[800] : '#fff',
border: '1px solid',
borderColor:
mode === 'dark'
? globalTheme.palette.primaryDark[700]
: globalTheme.palette.grey[200],
},
},
},
MuiAvatar: {
styleOverrides: {
root: {
width: 50,
height: 50,
borderRadius: 99,
},
},
},
MuiSwich: globalTheme.components?.MuiSwitch,
MuiChip: {
styleOverrides: {
filled: {
fontWeight: 'medium',
'&.MuiChip-colorSuccess': {
backgroundColor:
mode === 'dark'
? globalTheme.palette.success[900]
: globalTheme.palette.success[100],
color:
mode === 'dark'
? globalTheme.palette.success[100]
: globalTheme.palette.success[900],
},
'&.MuiChip-colorDefault': {
backgroundColor:
mode === 'dark'
? globalTheme.palette.primaryDark[700]
: globalTheme.palette.grey[100],
color:
mode === 'dark'
? globalTheme.palette.grey[200]
: globalTheme.palette.grey[800],
},
},
},
},
},
})
: createTheme({ palette: { mode: globalTheme.palette.mode } }),
[customized, globalTheme, mode],
);
const highlightedLines = element.id ? lineMapping[element.id] : null;
let startLine;
let endLine;
if (highlightedLines !== null) {
startLine = Array.isArray(highlightedLines) ? highlightedLines[0] : highlightedLines;
endLine = Array.isArray(highlightedLines) ? highlightedLines[1] : startLine;
}
return (
<ShowcaseContainer
preview={
<ThemeProvider theme={theme}>
<PointerContainer
onElementChange={setElement}
sx={{ minWidth: 300, width: '100%', maxWidth: '100%' }}
>
<MaterialDesignDemo />
</PointerContainer>
</ThemeProvider>
}
code={
<React.Fragment>
<MaterialVsCustomToggle customized={customized} setCustomized={setCustomized} />
<ShowcaseCodeWrapper maxHeight={320} hasDesignToggle>
{startLine !== undefined && (
<FlashCode startLine={startLine} endLine={endLine} sx={{ m: 1, mt: 7 }} />
)}
<HighlightedCode copyButtonHidden code={componentCode} language="jsx" plainStyle />
</ShowcaseCodeWrapper>
<MoreInfoBox
primaryBtnLabel="Start with Material UI"
primaryBtnHref={ROUTES.materialDocs}
secondaryBtnLabel="View all components"
secondaryBtnHref={ROUTES.materialAllComponents}
/>
</React.Fragment>
}
/>
);
}

View File

@@ -0,0 +1,332 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import { AvatarProps } from '@mui/material/Avatar';
import Box, { BoxProps } from '@mui/material/Box';
import Slide from 'docs/src/components/animation/Slide';
import FadeDelay from 'docs/src/components/animation/FadeDelay';
const ratio = 900 / 494;
// 'transparent' is interpreted as transparent black in Safari
// See https://css-tricks.com/thing-know-gradients-transparent-black/
const transparent = 'rgba(255,255,255,0)';
const Image = styled('img')(({ theme }) => ({
display: 'block',
width: 200,
height: 200 / ratio,
[theme.breakpoints.up('sm')]: {
width: 300,
height: 300 / ratio,
},
[theme.breakpoints.up('md')]: {
width: 450,
height: 450 / ratio,
},
border: '6px solid',
borderColor: (theme.vars || theme).palette.grey[400],
borderRadius: theme.shape.borderRadius,
objectFit: 'cover',
transitionProperty: 'all',
transitionDuration: '150ms',
boxShadow: '0 4px 20px rgba(61, 71, 82, 0.2)',
...theme.applyDarkStyles({
borderColor: (theme.vars || theme).palette.grey[800],
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.6)',
}),
}));
const Anchor = styled('a')(({ theme }) => [
{
display: 'inline-block',
position: 'relative',
transition: 'all 120ms ease',
borderRadius: '50%',
border: '1px solid',
borderColor: (theme.vars || theme).palette.grey[200],
boxShadow: `0 2px 12px ${alpha(theme.palette.primary[200], 0.3)}`,
backgroundColor: '#FFF',
'&:hover, &:focus': {
borderColor: (theme.vars || theme).palette.primary[300],
boxShadow: `0 4px 20px ${alpha(theme.palette.primary[400], 0.3)}`,
backgroundColor: (theme.vars || theme).palette.primary[50],
},
} as const,
theme.applyDarkStyles({
backgroundColor: alpha(theme.palette.primaryDark[900], 0.8),
borderColor: (theme.vars || theme).palette.primaryDark[600],
boxShadow: `0 2px 12px ${alpha(theme.palette.primaryDark[800], 0.5)}`,
'&:hover, &:focus': {
backgroundColor: alpha(theme.palette.primary[900], 0.8),
borderColor: (theme.vars || theme).palette.primary[700],
boxShadow: `0 2px 16px 0 ${alpha(theme.palette.primary[800], 0.5)}`,
},
}),
]);
const DesignToolLink = React.forwardRef<
HTMLAnchorElement,
React.PropsWithChildren<{ brand: 'figma' | 'sketch' }>
>(function DesignToolLink(props, ref) {
const { brand, ...other } = props;
return (
<Anchor
ref={ref}
aria-label="Go to MUI Store"
href={
{
figma:
'https://mui.com/store/items/figma-react/?utm_source=marketing&utm_medium=referral&utm_campaign=home-products',
sketch:
'https://mui.com/store/items/sketch-react/?utm_source=marketing&utm_medium=referral&utm_campaign=home-products',
}[brand]
}
target="_blank"
{...other}
/>
);
});
const DesignToolLogo = React.forwardRef<
HTMLImageElement,
{ brand: 'figma' | 'sketch' } & AvatarProps
>(function DesignToolLogo({ brand, ...props }, ref) {
return (
<Box
ref={ref}
{...props}
sx={{
display: 'flex',
p: 2,
borderRadius: '50%',
...(Array.isArray(props.sx) ? props.sx : [props.sx]),
}}
>
<img
src={`/static/branding/design-kits/${brand}-logo.svg`}
alt=""
loading="lazy"
width="60"
height="60"
/>
</Box>
);
});
export function PrefetchDesignKitImages() {
return (
<Box
sx={{
width: 0,
height: 0,
position: 'fixed',
top: -1000,
zIndex: -1,
'& > img': {
position: 'absolute',
},
}}
>
<img src="/static/branding/design-kits/designkits1.jpeg" alt="" loading="lazy" />
<img src="/static/branding/design-kits/designkits2.jpeg" alt="" loading="lazy" />
<img src="/static/branding/design-kits/designkits3.jpeg" alt="" loading="lazy" />
<img src="/static/branding/design-kits/designkits4.jpeg" alt="" loading="lazy" />
<img src="/static/branding/design-kits/designkits5.jpeg" alt="" loading="lazy" />
<img src="/static/branding/design-kits/designkits6.jpeg" alt="" loading="lazy" />
<img src="/static/branding/design-kits/designkits-figma.png" alt="" loading="lazy" />
<img src="/static/branding/design-kits/designkits-sketch.png" alt="" loading="lazy" />
<img src="/static/branding/design-kits/designkits-xd.png" alt="" loading="lazy" />
</Box>
);
}
const defaultSlideUp = {
'0%': {
transform: 'translateY(-300px)',
},
'100%': {
transform: 'translateY(-20px)',
},
};
export function DesignKitImagesSet1({
keyframes = defaultSlideUp,
...props
}: BoxProps & { keyframes?: Record<string, object> }) {
return (
<Slide animationName="designkit-slideup" {...props} keyframes={keyframes}>
<FadeDelay delay={400}>
<Image src="/static/branding/design-kits/designkits1.jpeg" alt="" />
</FadeDelay>
<FadeDelay delay={200}>
<Image src="/static/branding/design-kits/designkits3.jpeg" alt="" />
</FadeDelay>
<FadeDelay delay={0}>
<Image src="/static/branding/design-kits/designkits5.jpeg" alt="" />
</FadeDelay>
</Slide>
);
}
const defaultSlideDown = {
'0%': {
transform: 'translateY(150px)',
},
'100%': {
transform: 'translateY(-80px)',
},
};
export function DesignKitImagesSet2({
keyframes = defaultSlideDown,
...props
}: BoxProps & { keyframes?: Record<string, object> }) {
return (
<Slide animationName="designkit-slidedown" {...props} keyframes={keyframes}>
<FadeDelay delay={100}>
<Image src="/static/branding/design-kits/designkits2.jpeg" alt="" />
</FadeDelay>
<FadeDelay delay={300}>
<Image src="/static/branding/design-kits/designkits4.jpeg" alt="" />
</FadeDelay>
<FadeDelay delay={500}>
<Image src="/static/branding/design-kits/designkits6.jpeg" alt="" />
</FadeDelay>
</Slide>
);
}
export function DesignKitTools({ disableLink, ...props }: { disableLink?: boolean } & BoxProps) {
function renderTool(brand: 'figma' | 'sketch') {
if (disableLink) {
return <DesignToolLogo brand={brand} />;
}
return (
<DesignToolLink brand={brand}>
<DesignToolLogo brand={brand} />
</DesignToolLink>
);
}
return (
<Box
{...props}
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 10,
display: 'grid',
gap: { xs: 3, lg: 6 },
py: 4,
gridTemplateColumns: '1fr 1fr',
'& .MuiAvatar-root': {
width: { xs: 80, sm: 100 },
height: { xs: 80, sm: 100 },
},
...props.sx,
}}
>
<FadeDelay delay={200}>{renderTool('figma')}</FadeDelay>
<FadeDelay delay={400}>{renderTool('sketch')}</FadeDelay>
</Box>
);
}
export default function DesignKits() {
return (
<Box
sx={{
mx: { xs: -2, sm: -3, md: 0 },
my: { md: -18 },
height: { xs: 300, sm: 360, md: 'calc(100% + 320px)' },
overflow: 'hidden',
position: 'relative',
width: { xs: '100vw', md: '50vw' },
}}
>
<Box
sx={(theme) => ({
position: 'absolute',
width: '100%',
height: '100%',
bgcolor: 'grey.50',
opacity: 0.6,
zIndex: 1,
...theme.applyDarkStyles({
bgcolor: 'primaryDark.900',
}),
})}
/>
<Box
sx={(theme) => ({
display: { xs: 'block', md: 'none' },
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
background: `linear-gradient(to bottom, ${
(theme.vars || theme).palette.primary[50]
} 0%, ${transparent} 30%, ${transparent} 70%, ${
(theme.vars || theme).palette.primary[50]
} 100%)`,
zIndex: 2,
...theme.applyDarkStyles({
background: `linear-gradient(to bottom, ${
(theme.vars || theme).palette.primaryDark[900]
} 0%, ${alpha(theme.palette.primaryDark[900], 0)} 30%, ${alpha(
theme.palette.primaryDark[900],
0,
)} 70%, ${(theme.vars || theme).palette.primaryDark[900]} 100%)`,
}),
})}
/>
<Box
sx={(theme) => ({
display: { xs: 'none', md: 'block' },
position: 'absolute',
top: 0,
left: 0,
width: 400,
height: '100%',
background: `linear-gradient(to right, ${
(theme.vars || theme).palette.primary[50]
}, ${transparent})`,
zIndex: 2,
...theme.applyDarkStyles({
background: `linear-gradient(to right, ${
(theme.vars || theme).palette.primaryDark[900]
}, ${alpha(theme.palette.primaryDark[900], 0)})`,
}),
})}
/>
<DesignKitTools
sx={{
top: { xs: '50%', md: 'calc(50% + 80px)', xl: '50%' },
transform: { xs: 'translate(-50%, -50%)' },
left: { xs: 'min(50%, 500px)' },
}}
/>
<Box
sx={{
// need perspective on this wrapper to work in Safari
position: 'relative',
height: '100%',
perspective: '1000px',
}}
>
<Box
sx={{
left: '36%',
position: 'absolute',
display: 'flex',
transform: 'translateX(-40%) rotateZ(30deg) rotateX(8deg) rotateY(-8deg)',
transformOrigin: 'center center',
}}
>
<DesignKitImagesSet1 />
<DesignKitImagesSet2 sx={{ ml: { xs: 2, sm: 4, md: 8 } }} />
</Box>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,49 @@
import dynamic from 'next/dynamic';
import { useInView } from 'react-intersection-observer';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import GradientText from 'docs/src/components/typography/GradientText';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
function Placeholder() {
return (
<Box
sx={(theme) => ({
height: { xs: 1484, sm: 825, md: 601 },
borderRadius: 1,
bgcolor: 'grey.100',
...theme.applyDarkStyles({
bgcolor: 'primaryDark.900',
}),
})}
/>
);
}
const MaterialDesignComponents = dynamic(() => import('./MaterialDesignComponents'), {
loading: Placeholder,
});
export default function DesignSystemComponents() {
const { ref, inView } = useInView({
triggerOnce: true,
threshold: 0,
rootMargin: '500px',
});
return (
<Section ref={ref} cozy>
<SectionHeadline
alwaysCenter
overline="Production-ready components"
title={
<Typography variant="h2">
Beautiful and powerful,
<br /> <GradientText>right out of the box</GradientText>
</Typography>
}
/>
{inView ? <MaterialDesignComponents /> : <Placeholder />}
</Section>
);
}

View File

@@ -0,0 +1,103 @@
import { useInView } from 'react-intersection-observer';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import AddRounded from '@mui/icons-material/AddRounded';
import { Link } from '@mui/docs/Link';
import SponsorCard from 'docs/src/components/home/SponsorCard';
const DIAMONDs = [
{
src: '/static/sponsors/doit-square.svg',
name: 'Doit International',
description: 'Technology and cloud expertise to buy, optimize and manage public cloud.',
href: 'https://www.doit.com/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
];
export default function DiamondSponsors() {
const { ref, inView } = useInView({
triggerOnce: true,
threshold: 0,
rootMargin: '500px',
});
const maxNumberOfDiamondSponsors = 3;
const spotIsAvailable = maxNumberOfDiamondSponsors > DIAMONDs.length;
return (
<div ref={ref}>
<Typography
component="h3"
variant="h6"
sx={[
{
fontWeight: 'semiBold',
},
(theme) => ({
mt: 4,
mb: 1.5,
background: `linear-gradient(45deg, ${(theme.vars || theme).palette.primary[400]} 50%, ${
(theme.vars || theme).palette.primary[800]
} 100%)`,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}),
]}
>
Diamond
</Typography>
<Grid container spacing={{ xs: 2, md: 3 }}>
{DIAMONDs.map((item) => (
<Grid key={item.name} size={{ xs: 12, sm: 6, md: 4 }}>
<SponsorCard logoSize={64} inView={inView} item={item} />
</Grid>
))}
{spotIsAvailable && (
<Grid size={{ xs: 12, sm: 6, md: 4 }}>
<Paper
variant="outlined"
sx={{
p: 2,
display: 'flex',
alignItems: 'center',
height: '100%',
borderStyle: 'dashed',
}}
>
<IconButton
aria-label="Become MUI sponsor"
component="a"
href="mailto:sales@mui.com"
target="_blank"
rel="noopener"
color="primary"
sx={(theme) => ({
mr: 2,
border: '1px solid',
borderColor: 'grey.300',
...theme.applyDarkStyles({
borderColor: 'primaryDark.600',
}),
})}
>
<AddRounded />
</IconButton>
<div>
<Typography variant="body2" sx={{ color: 'text.primary', fontWeight: 'semiBold' }}>
Become our sponsor!
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
To join us, contact us at{' '}
<Link href="mailto:sales@mui.com" target="_blank" rel="noopener">
sales@mui.com
</Link>{' '}
for pre-approval.
</Typography>
</div>
</Paper>
</Grid>
)}
</Grid>
</div>
);
}

View File

@@ -0,0 +1,117 @@
import * as React from 'react';
import Box, { BoxProps } from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { debounce } from '@mui/material/utils';
const PointerContext = React.createContext<undefined | ((data: Data) => void)>(undefined);
export const withPointer = <T extends React.ElementType>(
Component: T,
options: { id: string; name: string },
) => {
function WithPointer(props: object) {
const root = React.useRef<HTMLElement>(null);
const handleMouseOver = React.useContext(PointerContext);
return (
<React.Fragment>
{/* @ts-ignore */}
<Component
ref={root}
{...props}
onMouseOver={(event: React.MouseEvent) => {
event.stopPropagation();
if (handleMouseOver && root.current) {
handleMouseOver({
id: options.id,
target: root.current,
name: options.name,
});
}
}}
/>
</React.Fragment>
);
}
return WithPointer as T;
};
export type Data = { id: null | string; name: null | string; target: null | HTMLElement };
export default function PointerContainer({
onElementChange,
...props
}: BoxProps & { onElementChange?: (data: Data) => void }) {
const container = React.useRef<HTMLDivElement>(null);
const [data, setData] = React.useState<Data>({
id: null,
name: null,
target: null,
});
const handleMouseOver = React.useMemo(
() =>
debounce((elementData: Data) => {
setData(elementData);
}, 200),
[],
);
React.useEffect(() => {
if (onElementChange) {
onElementChange(data);
}
}, [data, onElementChange]);
return (
<PointerContext.Provider value={handleMouseOver}>
<Box
ref={container}
{...props}
onMouseLeave={() => handleMouseOver({ id: null, name: null, target: null })}
sx={{ position: 'relative', ...props.sx }}
>
{props.children}
{container.current && data.target && (
<Box
sx={{
border: '1px solid',
borderColor: '#0072E5',
pointerEvents: 'none',
position: 'absolute',
zIndex: 10,
transition: 'none !important',
...(() => {
const containerRect = container.current.getBoundingClientRect();
const targetRect = data.target.getBoundingClientRect();
return {
top: targetRect.top - containerRect.top,
left: targetRect.left - containerRect.left,
width: `${targetRect.width}px`,
height: `${targetRect.height}px`,
};
})(),
}}
>
<Box
sx={{
bgcolor: '#0072E5',
borderTopLeftRadius: '2px',
borderTopRightRadius: '2px',
px: 0.5,
position: 'absolute',
top: 0,
transform: 'translateY(-100%)',
left: -1,
}}
>
<Typography
color="#fff"
sx={{ fontSize: '0.625rem', fontWeight: 500, display: 'block' }}
>
{data.name}
</Typography>
</Box>
</Box>
)}
</Box>
</PointerContext.Provider>
);
}

View File

@@ -0,0 +1,107 @@
import * as React from 'react';
import copy from 'clipboard-copy';
import Box, { BoxProps } from '@mui/material/Box';
import Button from '@mui/material/Button';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import ContentCopyRounded from '@mui/icons-material/ContentCopyRounded';
import CheckRounded from '@mui/icons-material/CheckRounded';
import { Link } from '@mui/docs/Link';
import NpmCopyButton from 'docs/src/components/action/NpmCopyButton';
interface GetStartedButtonsProps extends BoxProps {
primaryLabel?: string;
primaryUrl: string;
primaryUrlTarget?: string;
secondaryLabel?: string;
secondaryUrl?: string;
secondaryUrlTarget?: string;
installation?: string;
altInstallation?: string;
}
export default function GetStartedButtons(props: GetStartedButtonsProps) {
const [copied, setCopied] = React.useState(false);
const {
primaryLabel = 'Get started',
primaryUrl,
primaryUrlTarget = '_self',
secondaryLabel,
secondaryUrl,
secondaryUrlTarget = '_self',
installation,
altInstallation,
...other
} = props;
const handleCopy = () => {
setCopied(true);
copy(installation!).then(() => {
setTimeout(() => setCopied(false), 2000);
});
};
return (
<React.Fragment>
<Box
{...other}
sx={{
display: 'flex',
flexWrap: { xs: 'wrap', md: 'nowrap' },
gap: 1.5,
'&& > *': {
minWidth: { xs: '100%', md: '0%' },
},
...other.sx,
}}
>
<Button
href={primaryUrl}
component={Link}
target={primaryUrlTarget}
rel={primaryUrlTarget ? 'noopener' : ''}
noLinkStyle
variant="contained"
endIcon={<KeyboardArrowRightRounded />}
sx={{ flexShrink: 0 }}
>
{primaryLabel}
</Button>
{installation ? (
<Button
// @ts-expect-error
variant="codeOutlined"
endIcon={copied ? <CheckRounded color="primary" /> : <ContentCopyRounded />}
onClick={handleCopy}
sx={{
maxWidth: '324px',
display: 'inline-block',
justifyContent: 'start',
overflowX: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
position: 'relative',
pr: 5,
}}
>
{installation}
</Button>
) : null}
{secondaryLabel ? (
<Button
href={secondaryUrl}
component={Link}
target={secondaryUrlTarget}
rel={secondaryUrlTarget ? 'noopener' : ''}
noLinkStyle
variant="outlined"
color="secondary"
endIcon={<KeyboardArrowRightRounded />}
>
{secondaryLabel}
</Button>
) : null}
</Box>
{altInstallation && <NpmCopyButton installation={altInstallation} sx={{ mt: 2 }} />}
</React.Fragment>
);
}

View File

@@ -0,0 +1,164 @@
import { useInView } from 'react-intersection-observer';
import Paper from '@mui/material/Paper';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import AddRounded from '@mui/icons-material/AddRounded';
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import SponsorCard from 'docs/src/components/home/SponsorCard';
import BacklinkSponsor from 'docs/src/components/home/BacklinkSponsor';
import { Link } from '@mui/docs/Link';
import ROUTES from 'docs/src/route';
const GOLDs = [
{
src: '/static/sponsors/tidelift.svg',
name: 'Tidelift',
description: 'Enterprise-ready open-source software.',
href: 'https://tidelift.com/',
},
{
src: 'https://images.opencollective.com/dialmycalls/f5ae9ab/avatar/40.png',
srcSet: 'https://images.opencollective.com/dialmycalls/f5ae9ab/avatar/120.png 3x',
name: 'DialMyCalls',
description: 'Send text messages, calls, and emails.',
href: 'https://www.dialmycalls.com/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
{
src: '/static/sponsors/wispr-square-light.svg',
srcSet: '/static/sponsors/wispr-square-light.svg 3x',
srcDark: '/static/sponsors/wispr-square-dark.svg',
name: 'Wispr Flow',
description: 'AI Dictation: from speech to clear, polished text.',
href: 'https://ref.wisprflow.ai/ZSPYrru?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
];
const BACKLINKs = [
{
name: 'Goread.io',
description: 'Instagram followers, likes, views, and comments.',
href: 'https://goread.io/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
{
name: 'Buzzoid',
description: 'Instant delivery Instagram followers.',
href: 'https://buzzoid.com/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
{
name: 'Twicsy',
description: 'Instant delivery Instagram followers.',
href: 'https://twicsy.com/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
{
name: 'Views4You',
description: 'Social media growth services.',
href: 'https://views4you.com/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
{
name: 'Poprey',
description: 'Buy Instagram likes with crypto.',
href: 'https://poprey.com/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
{
name: 'SocialWick',
description: 'Buy Instagram followers.',
href: 'https://www.socialwick.com/instagram/followers',
},
{
name: 'Follower24',
description: 'Social media success.',
href: 'https://www.follower24.de/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
{
name: 'Reputation Manage',
description: 'Instant Delivery Google Reviews.',
href: 'https://reputationmanage.co/?utm_source=mui.com&utm_medium=referral&utm_content=homepage',
},
];
export default function GoldSponsors() {
const { ref, inView } = useInView({
triggerOnce: true,
threshold: 0,
rootMargin: '500px',
});
return (
<div ref={ref}>
<Typography
component="h3"
variant="h6"
sx={[
{
fontWeight: 'semiBold',
},
(theme) => ({
mt: 4,
mb: 1.5,
background: `linear-gradient(90deg, ${(theme.vars || theme).palette.warning[500]} 50%, ${
(theme.vars || theme).palette.warning[700]
} 100%)`,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
...theme.applyDarkStyles({
background: `linear-gradient(90deg, ${
(theme.vars || theme).palette.warning[400]
} 50%, ${(theme.vars || theme).palette.warning[700]} 100%)`,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}),
}),
]}
>
Gold
</Typography>
<Grid container spacing={{ xs: 2, md: 3 }}>
{GOLDs.map((item) => (
<Grid key={item.name} size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<SponsorCard inView={inView} item={item} />
</Grid>
))}
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<Paper
variant="outlined"
sx={{
p: 2,
height: '100%',
display: 'flex',
alignItems: 'center',
gap: 2,
borderStyle: 'dashed',
}}
>
<IconButton
aria-label="Sponsor MUI"
component="a"
href={ROUTES.goldSponsor}
target="_blank"
rel="noopener"
color="primary"
>
<AddRounded />
</IconButton>
<div>
<Typography variant="body2" sx={{ color: 'text.primary', fontWeight: 'semiBold' }}>
Become a sponsor
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Find out how{' '}
<Link href={ROUTES.goldSponsor} target="_blank" rel="noopener">
you can support MUI.
</Link>
</Typography>
</div>
</Paper>
</Grid>
</Grid>
<Box sx={{ maxWidth: 1000, mt: { xs: 2, md: 3 } }}>
{BACKLINKs.map((item, index) => (
<BacklinkSponsor key={index} item={item} />
))}
</Box>
</div>
);
}

View File

@@ -0,0 +1,144 @@
import * as React from 'react';
import dynamic from 'next/dynamic';
import { useTheme } from '@mui/material/styles';
import Box, { BoxProps } from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import useMediaQuery from '@mui/material/useMediaQuery';
import GradientText from 'docs/src/components/typography/GradientText';
import GetStartedButtons from 'docs/src/components/home/GetStartedButtons';
import HeroContainer from 'docs/src/layouts/HeroContainer';
function createLoading(sx: BoxProps['sx']) {
return function Loading() {
return (
<Box
sx={[
(theme) => ({
borderRadius: 1,
bgcolor: 'grey.100',
...theme.applyDarkStyles({
bgcolor: 'primaryDark.800',
}),
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
/>
);
};
}
const TaskCard = dynamic(() => import('../showcase/TaskCard'), {
ssr: false,
loading: createLoading({ width: 360, height: 280 }),
});
const PlayerCard = dynamic(() => import('../showcase/PlayerCard'), {
ssr: false,
loading: createLoading({ width: 400, height: 134 }),
});
const ThemeToggleButton = dynamic(() => import('../showcase/ThemeToggleButton'), {
ssr: false,
loading: createLoading({ width: 360, height: 48 }),
});
const ThemeChip = dynamic(() => import('../showcase/ThemeChip'), {
ssr: false,
loading: createLoading({ width: 360, height: 24 }),
});
const ThemeTimeline = dynamic(() => import('../showcase/ThemeTimeline'), {
ssr: false,
loading: createLoading({ width: 400, height: 175 }),
});
const FolderTable = dynamic(() => import('../showcase/FolderTable'), {
ssr: false,
loading: createLoading({ width: 400, height: 294 }),
});
const ThemeDatePicker = dynamic(() => import('../showcase/ThemeDatePicker'), {
ssr: false,
loading: createLoading({ width: 360, height: 245 }),
});
const ThemeTabs = dynamic(() => import('../showcase/ThemeTabs'), {
ssr: false,
loading: createLoading({ width: { md: 360, xl: 400 }, height: 48 }),
});
const ThemeSlider = dynamic(() => import('../showcase/ThemeSlider'), {
ssr: false,
loading: createLoading({ width: 400, height: 104 }),
});
const ThemeAccordion = dynamic(() => import('../showcase/ThemeAccordion'), {
ssr: false,
loading: createLoading({ width: 360, height: 252 }),
});
const NotificationCard = dynamic(() => import('../showcase/NotificationCard'), {
ssr: false,
loading: createLoading({ width: 360, height: 98 }),
});
export default function Hero() {
const globalTheme = useTheme();
const isMdUp = useMediaQuery(globalTheme.breakpoints.up('md'));
return (
<HeroContainer
linearGradient
left={
<Box sx={{ textAlign: { xs: 'center', md: 'left' }, maxWidth: 500 }}>
<Typography variant="h1" sx={{ mb: 1 }}>
<GradientText>Move faster</GradientText> <br />
with intuitive React UI tools
</Typography>
<Typography sx={{ color: 'text.secondary', mb: 3 }}>
MUI offers a comprehensive suite of free UI tools to help you ship new features faster.
Start with Material UI, our fully-loaded component library, or bring your own design
system to our production-ready components.
</Typography>
<GetStartedButtons primaryLabel="Discover the Core libraries" primaryUrl="/core/" />
</Box>
}
rightSx={{
p: 4,
ml: 2,
minWidth: 2000,
overflow: 'hidden', // the components in the Hero section are mostly illustrative, even though they're interactive. That's why scrolling is disabled.
'& > div': {
width: 360,
display: 'inline-flex',
verticalAlign: 'top',
'&:nth-of-type(2)': {
width: { xl: 400 },
},
},
'&& *': {
fontFamily: ['"IBM Plex Sans"', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'].join(
',',
),
},
}}
right={
<React.Fragment>
{isMdUp && (
<Stack spacing={3} useFlexGap sx={{ '& > .MuiPaper-root': { maxWidth: 'none' } }}>
<TaskCard />
<ThemeChip />
<ThemeDatePicker />
<NotificationCard />
<ThemeAccordion />
</Stack>
)}
{isMdUp && (
<Stack
spacing={3}
useFlexGap
sx={{ ml: 3, '& > .MuiPaper-root': { maxWidth: 'none' } }}
>
<ThemeTimeline />
<ThemeToggleButton />
<ThemeSlider />
<ThemeTabs />
<PlayerCard />
<FolderTable />
</Stack>
)}
</React.Fragment>
}
/>
);
}

View File

@@ -0,0 +1,45 @@
import dynamic from 'next/dynamic';
import { useInView } from 'react-intersection-observer';
import Box from '@mui/material/Box';
import { alpha } from '@mui/material/styles';
import Section from 'docs/src/layouts/Section';
function Placeholder() {
return (
<Box
sx={{
height: {
xs: 202,
sm: 180,
md: 193,
},
}}
/>
);
}
const StartToday = dynamic(() => import('./StartToday'), { loading: Placeholder });
export default function HeroEnd() {
const { ref, inView } = useInView({
triggerOnce: true,
threshold: 0,
rootMargin: '500px',
});
return (
<Box
ref={ref}
sx={(theme) => ({
background: `linear-gradient(180deg, #FFF 50%, ${(theme.vars || theme).palette.primary[50]} 100%)`,
...theme.applyDarkStyles({
background: `linear-gradient(180deg, ${
(theme.vars || theme).palette.primaryDark[900]
} 50%, ${alpha(theme.palette.primary[900], 0.2)} 100%)`,
}),
})}
>
<Section bg="transparent" cozy>
{inView ? <StartToday /> : <Placeholder />}
</Section>
</Box>
);
}

View File

@@ -0,0 +1,687 @@
import * as React from 'react';
import {
styled,
Theme,
ThemeOptions,
alpha,
extendTheme,
CssVarsProvider,
} from '@mui/material/styles';
import { capitalize } from '@mui/material/utils';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Tabs from '@mui/material/Tabs';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import Paper from '@mui/material/Paper';
import Tab from '@mui/material/Tab';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import ShoppingCartRounded from '@mui/icons-material/ShoppingCartRounded';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import CheckCircleRounded from '@mui/icons-material/CheckCircleRounded';
import MailRounded from '@mui/icons-material/MailRounded';
import VerifiedUserRounded from '@mui/icons-material/VerifiedUserRounded';
import HelpCenterRounded from '@mui/icons-material/HelpCenterRounded';
import ROUTES from 'docs/src/route';
import { Link } from '@mui/docs/Link';
import { getDesignTokens, getThemedComponents } from '@mui/docs/branding';
const Grid = styled('div')(({ theme }) => [
{
borderRadius: (theme.vars || theme).shape.borderRadius,
backgroundColor: alpha(theme.palette.grey[50], 0.4),
display: 'grid',
gridTemplateColumns: '1fr',
gridAutoRows: 240,
[theme.breakpoints.up('sm')]: {
gridAutoRows: 260,
paddingTop: 1,
gridTemplateColumns: '1fr 1fr',
},
[theme.breakpoints.up('md')]: {
gridAutoRows: 280,
gridTemplateColumns: '1fr 1fr 1fr',
},
'& > div': {
padding: theme.spacing(2),
alignSelf: 'stretch',
border: '1px solid',
borderColor: (theme.vars || theme).palette.grey[200],
[theme.breakpoints.only('xs')]: {
'&:first-of-type': {
borderTopLeftRadius: (theme.vars || theme).shape.borderRadius,
borderTopRightRadius: (theme.vars || theme).shape.borderRadius,
},
'&:last-of-type': {
borderBottomLeftRadius: (theme.vars || theme).shape.borderRadius,
borderBottomRightRadius: (theme.vars || theme).shape.borderRadius,
},
'&:not(:first-of-type)': {
marginTop: -1,
},
},
[theme.breakpoints.only('sm')]: {
marginTop: -1,
'&:first-of-type': {
borderTopLeftRadius: (theme.vars || theme).shape.borderRadius,
},
'&:last-of-type': {
borderBottomRightRadius: (theme.vars || theme).shape.borderRadius,
borderStyle: 'dashed',
},
'&:nth-of-type(even)': {
marginLeft: -1,
},
'&:nth-last-of-type(2)': {
borderBottomLeftRadius: (theme.vars || theme).shape.borderRadius,
},
'&:nth-of-type(2)': {
borderTopRightRadius: (theme.vars || theme).shape.borderRadius,
},
},
[theme.breakpoints.up('md')]: {
marginTop: -1,
'&:not(:nth-of-type(3n + 1))': {
marginLeft: -1,
},
'&:first-of-type': {
borderTopLeftRadius: (theme.vars || theme).shape.borderRadius,
},
'&:last-of-type': {
borderBottomRightRadius: (theme.vars || theme).shape.borderRadius,
},
'&:nth-last-of-type(3)': {
borderBottomLeftRadius: (theme.vars || theme).shape.borderRadius,
},
'&:nth-of-type(3)': {
borderTopRightRadius: (theme.vars || theme).shape.borderRadius,
},
},
},
},
theme.applyDarkStyles({
backgroundColor: (theme.vars || theme).palette.background.paper,
'& > div': {
borderColor: alpha(theme.palette.primaryDark[600], 0.3),
},
}),
]);
function Demo({
name,
children,
control,
...props
}: {
name: string;
theme: Theme | undefined;
children: React.ReactElement<unknown>;
control?: { prop: string; values: Array<string>; defaultValue?: string };
}) {
const [propValue, setPropValue] = React.useState(
control ? control.defaultValue || control.values[0] : '',
);
return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{control ? (
<Box sx={{ minHeight: 40, ml: -1, mt: -1 }}>
<Tabs
value={propValue}
onChange={(event, value) => setPropValue(value)}
sx={{
minHeight: 'initial',
'& .MuiTabs-indicator': {
bgcolor: 'transparent',
'&::before': {
height: '100%',
content: '""',
display: 'block',
width: (theme) => `calc(100% - ${theme.spacing(2)})`,
bgcolor: 'primary.main',
position: 'absolute',
top: 0,
left: (theme) => theme.spacing(1),
},
},
'& .MuiTab-root': {
px: 1,
pt: 0.5,
minWidth: 'initial',
minHeight: 'initial',
fontWeight: 'medium',
},
}}
>
{control.values.map((value) => (
<Tab key={value} value={value} label={capitalize(value)} />
))}
</Tabs>
</Box>
) : null}
<Box
className="mui-default-theme"
sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
<CssVarsProvider theme={props.theme}>
{React.cloneElement(children, {
...(control && {
[control.prop]: propValue,
}),
})}
</CssVarsProvider>
</Box>
<Typography variant="body2" sx={{ fontWeight: 'semiBold' }}>
{name}
</Typography>
</Box>
);
}
const StyledChip = styled(Chip)(({ theme }) => [
{
fontWeight: 700,
'&.MuiChip-outlined': {
color: (theme.vars || theme).palette.text.secondary,
},
'&.MuiChip-filled': {
borderColor: (theme.vars || theme).palette.primary[300],
backgroundColor: alpha(theme.palette.primary[100], 0.5),
color: (theme.vars || theme).palette.primary[600],
},
},
theme.applyDarkStyles({
'&.MuiChip-filled': {
borderColor: (theme.vars || theme).palette.primary[500],
backgroundColor: (theme.vars || theme).palette.primary[800],
color: (theme.vars || theme).palette.primary[100],
},
}),
]);
const themedComponents = getThemedComponents();
export function buildTheme(): ThemeOptions {
return {
components: {
MuiButtonBase: {
defaultProps: {
disableTouchRipple: true,
},
},
MuiButton: {
defaultProps: {
disableElevation: true,
},
styleOverrides: {
root: {
borderRadius: '99px',
fontWeight: 500,
fontSize: '0.875rem',
lineHeight: 24 / 16,
textTransform: 'none',
},
sizeSmall: ({ theme }) => ({
padding: theme.spacing(0.5, 1),
}),
sizeMedium: ({ theme }) => ({
padding: theme.spacing(0.8, 2),
}),
sizeLarge: ({ theme }) => ({
padding: theme.spacing(1, 2),
fontSize: '1rem',
}),
text: ({ theme }) => ({
color: (theme.vars || theme).palette.primary[600],
...theme.applyDarkStyles({
color: (theme.vars || theme).palette.primary[300],
}),
}),
contained: ({ theme }) => ({
color: (theme.vars || theme).palette.primaryDark[50],
backgroundColor: (theme.vars || theme).palette.primary[600],
boxShadow: '0 2px 0 rgba(255,255,255,0.1) inset, 0 -1px 0 rgba(0,0,0,0.1) inset',
border: '1px solid',
borderColor: (theme.vars || theme).palette.primary[600],
...theme.applyDarkStyles({
backgroundColor: (theme.vars || theme).palette.primary[600],
borderColor: (theme.vars || theme).palette.primary[800],
}),
}),
outlined: ({ theme }) => ({
borderColor: (theme.vars || theme).palette.primary[300],
...theme.applyDarkStyles({
color: (theme.vars || theme).palette.primary[300],
backgroundColor: alpha(theme.palette.primary[900], 0.1),
borderColor: alpha(theme.palette.primary[300], 0.5),
}),
}),
iconSizeSmall: {
'& > *:nth-of-type(1)': {
fontSize: '0.875rem',
},
},
iconSizeMedium: {
'& > *:nth-of-type(1)': {
fontSize: '0.875rem',
},
},
iconSizeLarge: {
'& > *:nth-of-type(1)': {
fontSize: '1rem',
},
},
},
},
MuiAlert: {
defaultProps: {
icon: <CheckCircleRounded />,
},
styleOverrides: {
root: ({ theme }) => [
{
padding: theme.spacing(1.5),
'& .MuiAlert-icon': {
color: (theme.vars || theme).palette.primaryDark[800],
},
},
theme.applyDarkStyles({
'& .MuiAlert-icon': {
color: (theme.vars || theme).palette.primaryDark[100],
},
}),
],
filled: ({ theme }) => ({
color: (theme.vars || theme).palette.primary[50],
backgroundColor: (theme.vars || theme).palette.primary[600],
'& .MuiAlert-icon': {
color: '#fff',
},
...theme.applyDarkStyles({
backgroundColor: (theme.vars || theme).palette.primary[600],
}),
}),
outlined: ({ theme }) => [
{
color: (theme.vars || theme).palette.primaryDark[700],
backgroundColor: '#fff',
borderColor: (theme.vars || theme).palette.primary[100],
'& .MuiAlert-icon': {
color: (theme.vars || theme).palette.primary[500],
},
},
theme.applyDarkStyles({
color: (theme.vars || theme).palette.primaryDark[50],
backgroundColor: 'transparent',
borderColor: (theme.vars || theme).palette.primaryDark[600],
'& .MuiAlert-icon': {
color: (theme.vars || theme).palette.primaryDark[100],
},
}),
],
message: {
padding: 0,
fontWeight: 500,
},
standardInfo: ({ theme }) => [
{
backgroundColor: (theme.vars || theme).palette.primary[50],
color: (theme.vars || theme).palette.primary[600],
border: '1px solid',
borderColor: alpha(theme.palette.primaryDark[100], 0.5),
'& .MuiAlert-icon': {
color: (theme.vars || theme).palette.primary[500],
},
},
theme.applyDarkStyles({
backgroundColor: alpha(theme.palette.primaryDark[700], 0.5),
color: (theme.vars || theme).palette.primaryDark[50],
borderColor: alpha(theme.palette.primaryDark[500], 0.2),
'& .MuiAlert-icon': {
color: (theme.vars || theme).palette.primaryDark[50],
},
}),
],
icon: {
paddingTop: 1,
paddingBottom: 0,
'& > svg': {
fontSize: '1.125rem',
},
},
},
},
MuiTextField: {
styleOverrides: {
root: ({ theme }) => [
{
'& .MuiInputLabel-outlined.Mui-focused': {
color: (theme.vars || theme).palette.grey[800],
},
'& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
background: 'transparent',
borderColor: (theme.vars || theme).palette.primary[400],
},
'& .MuiOutlinedInput-root': {
backgroundColor: 'transparent',
borderColor: (theme.vars || theme).palette.grey[50],
},
'& .MuiInputBase-root': {
fontWeight: 700,
'&::before': {
borderColor: (theme.vars || theme).palette.grey[300],
},
},
'& .MuiFilledInput-root': {
backgroundColor: '#fff',
border: '1px solid',
borderColor: (theme.vars || theme).palette.grey[100],
'&::before': {
borderColor: (theme.vars || theme).palette.grey[300],
},
'&::after': {
borderColor: (theme.vars || theme).palette.primary[400],
},
'&:hover': {
borderColor: (theme.vars || theme).palette.grey[200],
},
},
'& .MuiInputLabel-filled.Mui-focused': {
color: (theme.vars || theme).palette.grey[800],
},
'& .MuiInput-root.Mui-focused': {
'&::after': {
borderColor: (theme.vars || theme).palette.primary[400],
},
},
'& .MuiInputLabel-root.Mui-focused': {
color: (theme.vars || theme).palette.grey[800],
},
},
theme.applyDarkStyles({
'& .MuiInputBase-root': {
'&::before': {
borderColor: (theme.vars || theme).palette.primaryDark[500],
},
},
'& .MuiInputLabel-outlined.Mui-focused': {
color: (theme.vars || theme).palette.primary[300],
},
'& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: (theme.vars || theme).palette.primary[300],
},
'& .MuiOutlinedInput-input': {
borderRadius: 'inherit',
backgroundColor: (theme.vars || theme).palette.primaryDark[800],
},
'& .MuiFilledInput-root': {
borderColor: (theme.vars || theme).palette.primaryDark[700],
backgroundColor: alpha(theme.palette.primaryDark[900], 0.5),
'&::after': {
borderColor: (theme.vars || theme).palette.primary[300],
},
'&:hover': {
backgroundColor: alpha(theme.palette.primaryDark[700], 0.8),
borderColor: (theme.vars || theme).palette.primaryDark[600],
},
},
'& .MuiInputLabel-filled.Mui-focused': {
color: (theme.vars || theme).palette.grey[500],
},
'& .MuiInput-root.Mui-focused': {
'&::after': {
borderColor: (theme.vars || theme).palette.primaryDark[400],
},
},
'& .MuiInputLabel-root.Mui-focused': {
color: (theme.vars || theme).palette.grey[500],
},
}),
],
},
},
MuiTooltip: themedComponents.components?.MuiTooltip,
MuiPaper: themedComponents.components?.MuiPaper,
MuiTableHead: {
styleOverrides: {
root: ({ theme }) => ({
padding: 8,
backgroundColor: alpha(theme.palette.grey[50], 0.5),
borderColor: (theme.vars || theme).palette.divider,
...theme.applyDarkStyles({
backgroundColor: alpha(theme.palette.primaryDark[700], 0.5),
}),
}),
},
},
MuiTableCell: {
styleOverrides: {
root: ({ theme }) => ({
padding: 8,
borderColor: (theme.vars || theme).palette.divider,
}),
},
},
MuiPopover: {
styleOverrides: {
paper: ({ theme }) => ({
boxShadow: '0px 4px 20px rgba(170, 180, 190, 0.3)',
...theme.applyDarkStyles({
boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.2)',
}),
}),
},
},
MuiMenu: {
styleOverrides: {
list: {
padding: 0,
},
},
},
MuiMenuItem: {
styleOverrides: {
root: ({ theme }) => [
{
margin: theme.spacing(1),
padding: '4px 8px',
borderRadius: '8px',
'& .MuiListItemIcon-root': {
minWidth: '24px',
},
'& svg': {
fontSize: '1rem',
color: (theme.vars || theme).palette.grey[500],
},
},
theme.applyDarkStyles({
'& svg': {
color: (theme.vars || theme).palette.grey[400],
},
}),
],
},
},
},
};
}
const { palette: lightPalette, typography, ...designTokens } = getDesignTokens('light');
const { palette: darkPalette } = getDesignTokens('dark');
const defaultTheme = extendTheme({
colorSchemes: { light: true, dark: true },
colorSchemeSelector: 'data-mui-color-scheme',
});
export const customTheme = extendTheme({
cssVarPrefix: 'muidocs',
colorSchemeSelector: 'data-mui-color-scheme',
colorSchemes: {
light: {
palette: lightPalette,
},
dark: {
palette: darkPalette,
},
},
...designTokens,
...buildTheme(),
});
export default function MaterialDesignComponents() {
const [anchor, setAnchor] = React.useState<HTMLElement | null>(null);
const [customized, setCustomized] = React.useState(false);
const theme = customized ? customTheme : defaultTheme;
return (
<div>
<Box sx={{ mt: { xs: 2, md: 2 }, mb: 4, display: 'flex', justifyContent: 'center' }}>
<StyledChip
size="small"
label="Custom theme"
variant={customized ? 'filled' : 'outlined'}
color={customized ? 'primary' : 'secondary'}
onClick={() => setCustomized(true)}
sx={{ mr: 1 }}
/>
<StyledChip
size="small"
label="Material Design"
variant={!customized ? 'filled' : 'outlined'}
color={!customized ? 'primary' : 'secondary'}
onClick={() => setCustomized(false)}
/>
</Box>
<Grid>
<div>
<Demo
theme={theme}
name="Button"
control={{ prop: 'size', values: ['small', 'medium', 'large'], defaultValue: 'medium' }}
>
<Button variant="contained" startIcon={<ShoppingCartRounded />}>
Add to Cart
</Button>
</Demo>
</div>
<div>
<Demo
theme={theme}
name="Alert"
control={{ prop: 'variant', values: ['standard', 'filled', 'outlined'] }}
>
<Alert color="info">Check out this alert!</Alert>
</Demo>
</div>
<div>
<Demo
theme={theme}
name="Text Field"
control={{ prop: 'variant', values: ['outlined', 'standard', 'filled'] }}
>
<TextField id="material-design-textfield" label="Username" defaultValue="Ultraviolet" />
</Demo>
</div>
<div>
<Demo theme={theme} name="Menu">
<React.Fragment>
<Button onClick={(event) => setAnchor(event.target as HTMLElement)}>
Click to open
</Button>
<Menu
open={Boolean(anchor)}
anchorEl={anchor}
onClose={() => setAnchor(null)}
PaperProps={{ variant: 'outlined', elevation: 0 }}
>
<MenuItem>
<ListItemIcon>
<MailRounded />
</ListItemIcon>
Contact
</MenuItem>
<MenuItem>
<ListItemIcon>
<VerifiedUserRounded />
</ListItemIcon>
Security
</MenuItem>
<MenuItem>
<ListItemIcon>
<HelpCenterRounded />
</ListItemIcon>
About us
</MenuItem>
</Menu>
</React.Fragment>
</Demo>
</div>
<div>
<Demo theme={theme} name="Table">
<TableContainer
component={Paper}
variant="outlined"
sx={{
'& .MuiTableBody-root > .MuiTableRow-root:last-of-type > .MuiTableCell-root': {
borderBottomWidth: 0,
},
}}
>
<Table aria-label="demo table">
<TableHead>
<TableRow>
<TableCell>Dessert</TableCell>
<TableCell>Calories</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Frozen yoghurt</TableCell>
<TableCell>109</TableCell>
</TableRow>
<TableRow>
<TableCell>Cupcake</TableCell>
<TableCell>305</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Demo>
</div>
<Box
sx={{
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Typography variant="body2" sx={{ fontWeight: 'bold', mb: 0.5 }}>
Want to see more?
</Typography>
<Typography
variant="body2"
sx={{ color: 'text.secondary', mb: 0.5, maxWidth: 250, mx: 'auto' }}
>
Check out the docs for details of the complete library.
</Typography>
<Button
component={Link}
noLinkStyle
href={ROUTES.documentation}
endIcon={<KeyboardArrowRightRounded />}
>
Learn more
</Button>
</Box>
</Grid>
</div>
);
}

View File

@@ -0,0 +1,78 @@
import * as React from 'react';
import MuiChip from '@mui/material/Chip';
import MuiCardMedia from '@mui/material/CardMedia';
import MuiCard, { CardProps } from '@mui/material/Card';
import MuiSwitch from '@mui/material/Switch';
import MuiTypography from '@mui/material/Typography';
import MuiStack from '@mui/material/Stack';
import MuiRating from '@mui/material/Rating';
import { withPointer } from 'docs/src/components/home/ElementPointer';
export const componentCode = `
<Card>
<CardMedia
component="img"
alt="Yosemite National Park"
image="/static/images/cards/yosemite.jpeg"
/>
<Stack direction="row" alignItems="center" spacing={3} p={2} useFlexGap>
<Stack direction="column" spacing={0.5} useFlexGap>
<Typography>Yosemite National Park, California, USA</Typography>
<Stack direction="row" spacing={1} useFlexGap>
<Chip
size="small"
label={active ? 'Active' : 'Inactive'}
color={active ? 'success' : 'default'}
/>
<Rating defaultValue={4} size="small" />
</Stack>
</Stack>
<Switch checked={active} />
</Stack>
</Card>
`;
const Card = withPointer(MuiCard, { id: 'card', name: 'Card' });
const CardMedia = withPointer(MuiCardMedia, { id: 'cardmedia', name: 'CardMedia' });
const Stack = withPointer(MuiStack, { id: 'stack', name: 'Stack' });
const Stack2 = withPointer(MuiStack, { id: 'stack2', name: 'Stack' });
const Stack3 = withPointer(MuiStack, { id: 'stack3', name: 'Stack' });
const Typography = withPointer(MuiTypography, { id: 'typography', name: 'Typography' });
const Chip = withPointer(MuiChip, { id: 'chip', name: 'Chip' });
const Rating = withPointer(MuiRating, { id: 'rating', name: 'Rating' });
const Switch = withPointer(MuiSwitch, { id: 'switch', name: 'Switch' });
export default function MaterialDesignDemo(props: CardProps) {
const [active, setActive] = React.useState(true);
return (
<Card {...props} variant="outlined" sx={{ p: 2 }}>
<CardMedia
component="img"
alt="Yosemite National Park"
height="100"
image="/static/images/cards/yosemite.jpeg"
sx={{ borderRadius: 0.5 }}
/>
<Stack alignItems="center" direction="row" spacing={3} mt={2} useFlexGap>
<Stack2 direction="column" spacing={0.5} useFlexGap>
<Typography fontWeight="semiBold">Yosemite National Park, California, USA</Typography>
<Stack3 direction="row" spacing={1} useFlexGap>
<Chip
label={active ? 'Active' : 'Inactive'}
color={active ? 'success' : 'default'}
size="small"
sx={{ width: 'fit-content', fontSize: 12, height: 20, px: 0, zIndex: 2 }}
/>
<Rating name="Rating component" defaultValue={4} size="small" />
</Stack3>
</Stack2>
<Switch
inputProps={{ 'aria-label': active ? 'Active' : 'Inactive' }}
checked={active}
onChange={(event) => setActive(event.target.checked)}
sx={{ ml: 'auto' }}
/>
</Stack>
</Card>
);
}

View File

@@ -0,0 +1,62 @@
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
const data = [
{ title: '5.8M', metadata: 'Weekly downloads on npm' },
{ title: '93.9k', metadata: 'Stars on GitHub' },
{ title: '3.0k', metadata: 'Open-source contributors' },
{ title: '19.2k', metadata: 'Followers on X' },
];
export default function MuiStatistics() {
return (
<Box
data-mui-color-scheme="dark"
sx={(theme) => ({
pt: { xs: 1, sm: 2.5 },
pb: { xs: 2, sm: 3 },
pl: { xs: 2, sm: 0 },
pr: 0,
display: 'flex',
justifyContent: 'center',
gap: { xs: 0, sm: 1 },
width: '100%',
flexWrap: 'wrap',
background: `linear-gradient(180deg, ${alpha(
theme.palette.primary[900],
0.1,
)} 2%, transparent 80%)`,
})}
>
{data.map((item) => (
<Box key={item.title} sx={{ width: { xs: '50%', sm: 200 }, p: { xs: 1, sm: 0 } }}>
<Typography
variant="h4"
component="h3"
sx={[
{
fontWeight: 'semiBold',
},
(theme) => ({
textAlign: { xs: 'left', sm: 'center' },
color: 'primary.main',
...theme.applyDarkStyles({
color: 'primary.200',
}),
}),
]}
>
{item.title}
</Typography>
<Typography
variant="body2"
sx={{ color: 'text.secondary', textAlign: { xs: 'left', sm: 'center' } }}
>
{item.metadata}
</Typography>
</Box>
))}
</Box>
);
}

View File

@@ -0,0 +1,67 @@
import * as React from 'react';
import { useRouter } from 'next/router';
import Slide from '@mui/material/Slide';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import CloseRounded from '@mui/icons-material/CloseRounded';
import MarkEmailReadTwoTone from '@mui/icons-material/MarkEmailReadTwoTone';
export default function NewsletterToast() {
const router = useRouter();
const [hidden, setHidden] = React.useState(router.query.newsletter !== 'subscribed');
React.useEffect(() => {
if (router.query.newsletter === 'subscribed') {
setHidden(false);
router.replace(router.pathname);
}
}, [router.query.newsletter, router]);
React.useEffect(() => {
const time = setTimeout(() => {
if (!hidden) {
setHidden(true);
}
}, 4000);
return () => {
clearTimeout(time);
};
}, [hidden]);
return (
<Slide in={!hidden} timeout={400} direction="down">
<Box sx={{ position: 'fixed', zIndex: 1300, top: 80, left: 0, width: '100%' }}>
<Card
variant="outlined"
role="alert"
sx={(theme) => ({
p: 1,
position: 'absolute',
left: '50%',
transform: 'translate(-50%)',
opacity: hidden ? 0 : 1,
transition: '0.5s',
display: 'flex',
alignItems: 'center',
boxShadow: '0px 4px 20px rgba(61, 71, 82, 0.25)',
...theme.applyDarkStyles({
boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.6)',
}),
})}
>
<MarkEmailReadTwoTone color="success" sx={{ mx: 0.5 }} />
<div>
<Typography
variant="body2"
sx={{ color: 'text.secondary', fontWeight: 500, ml: 1, mr: 2 }}
>
You have subscribed to MUI newsletter.
</Typography>
</div>
<IconButton aria-hidden size="small" onClick={() => setHidden(true)} aria-label="close">
<CloseRounded fontSize="small" />
</IconButton>
</Card>
</Box>
</Slide>
);
}

View File

@@ -0,0 +1,91 @@
import * as React from 'react';
import dynamic from 'next/dynamic';
import { useInView } from 'react-intersection-observer';
import Grid from '@mui/material/Grid';
import Box, { BoxProps } from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import GradientText from 'docs/src/components/typography/GradientText';
import ProductsSwitcher from 'docs/src/components/home/ProductsSwitcher';
import { PrefetchStoreTemplateImages } from 'docs/src/components/home/StoreTemplatesBanner';
import { PrefetchDesignKitImages } from 'docs/src/components/home/DesignKits';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
function createLoading(sx: BoxProps['sx']) {
return function Loading() {
return (
<Box
sx={[
(theme) => ({
borderRadius: 1,
bgcolor: 'grey.100',
...theme.applyDarkStyles({
bgcolor: 'primaryDark.800',
}),
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
/>
);
};
}
const CoreShowcase = dynamic(() => import('./CoreShowcase'), {
loading: createLoading({ height: 630 }),
});
const AdvancedShowcase = dynamic(() => import('./AdvancedShowcase'), {
loading: createLoading({ height: 630 }),
});
const StoreTemplatesBanner = dynamic(() => import('./StoreTemplatesBanner'));
const DesignKits = dynamic(() => import('./DesignKits'));
export default function ProductSuite() {
const [productIndex, setProductIndex] = React.useState(0);
const { ref, inView } = useInView({
triggerOnce: true,
threshold: 0,
rootMargin: '200px',
});
return (
<Section bg="gradient" ref={ref} cozy>
<Grid container spacing={2}>
<Grid size={{ md: 6 }} sx={{ minHeight: { md: 630 } }}>
<SectionHeadline
overline="Products"
title={
<Typography variant="h2">
Every component you need is <GradientText>ready for production</GradientText>
</Typography>
}
description="Build at an accelerated pace without sacrificing flexibility or control."
/>
<ProductsSwitcher
inView={inView}
productIndex={productIndex}
setProductIndex={setProductIndex}
/>
</Grid>
<Grid
sx={productIndex === 0 ? { minHeight: { xs: 777, sm: 757, md: 'unset' } } : {}}
size={{ xs: 12, md: 6 }}
>
{inView ? (
<React.Fragment>
<PrefetchStoreTemplateImages />
<PrefetchDesignKitImages />
{productIndex === 0 && <CoreShowcase />}
{productIndex === 1 && <AdvancedShowcase />}
{productIndex === 2 && <StoreTemplatesBanner />}
{productIndex === 3 && <DesignKits />}
</React.Fragment>
) : (
<Box sx={{ height: { xs: 0, md: 690 } }} />
)}
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,150 @@
import * as React from 'react';
import dynamic from 'next/dynamic';
import { Theme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import useMediaQuery from '@mui/material/useMediaQuery';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import IconImage from 'docs/src/components/icon/IconImage';
import Highlighter from 'docs/src/components/action/Highlighter';
import SvgMuiLogomark from 'docs/src/icons/SvgMuiLogomark';
const SwipeableViews = dynamic(() => import('react-swipeable-views'), { ssr: false });
function ProductItem({
icon,
name,
description,
chip,
}: {
icon: React.ReactNode;
name: React.ReactNode;
description: React.ReactNode;
chip?: React.ReactNode;
}) {
return (
<Box
component="span"
sx={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Box
sx={{
p: 2,
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
alignItems: { md: 'center' },
gap: 2.5,
}}
>
<Box
sx={{
height: 32,
width: 32,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}
>
{icon}
</Box>
<span>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography color="text.primary" variant="body2" fontWeight="semiBold">
{name}
</Typography>
{chip}
</Box>
<Typography color="text.secondary" variant="body2" fontWeight="regular" sx={{ my: 0.5 }}>
{description}
</Typography>
</span>
</Box>
</Box>
);
}
export default function ProductsSwitcher(props: {
inView?: boolean;
productIndex: number;
setProductIndex: React.Dispatch<React.SetStateAction<number>>;
}) {
const { inView = false, productIndex, setProductIndex } = props;
const isBelowMd = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
const productElements = [
<ProductItem
name="Material UI"
description="Foundational components for shipping features faster."
icon={<SvgMuiLogomark height={28} width={28} />}
/>,
<ProductItem
name="MUI X"
description="Advanced components for complex use cases."
icon={<IconImage name="product-advanced" height={32} width={32} />}
/>,
<ProductItem
name="Templates"
description="Professionally built UIs to jumpstart your next project."
icon={<IconImage name="product-templates" height={32} width={32} />}
/>,
<ProductItem
name="Design kits"
description="The core components available on your favorite design tool."
icon={<IconImage name="product-designkits" height={32} width={32} />}
/>,
];
return (
<React.Fragment>
<Box
sx={{
display: { md: 'none' },
maxWidth: 'calc(100vw - 40px)',
minHeight: { xs: 200, sm: 166 },
'& > div': { pr: '32%' },
}}
>
{isBelowMd && inView && (
<SwipeableViews
index={productIndex}
resistance
enableMouseEvents
onChangeIndex={(index) => setProductIndex(index)}
>
{productElements.map((elm, index) => (
<Highlighter
key={index}
disableBorder
onClick={() => setProductIndex(index)}
selected={productIndex === index}
sx={{
width: '100%',
transition: '0.3s',
transform: productIndex !== index ? 'scale(0.9)' : 'scale(1)',
}}
>
{elm}
</Highlighter>
))}
</SwipeableViews>
)}
</Box>
<Stack spacing={1} sx={{ display: { xs: 'none', md: 'flex' }, maxWidth: 500 }}>
{productElements.map((elm, index) => (
<Highlighter
key={index}
disableBorder
onClick={() => setProductIndex(index)}
selected={productIndex === index}
>
{elm}
</Highlighter>
))}
</Stack>
</React.Fragment>
);
}

View File

@@ -0,0 +1,48 @@
import dynamic from 'next/dynamic';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import {
CORE_CUSTOMERS,
ADVANCED_CUSTOMERS,
DESIGNKITS_CUSTOMERS,
TEMPLATES_CUSTOMERS,
} from 'docs/src/components/home/CompaniesGrid';
export { CORE_CUSTOMERS, ADVANCED_CUSTOMERS, DESIGNKITS_CUSTOMERS, TEMPLATES_CUSTOMERS };
const CompaniesGrid = dynamic(() => import('./CompaniesGrid'));
export default function References({
companies,
}: {
companies:
| typeof CORE_CUSTOMERS
| typeof ADVANCED_CUSTOMERS
| typeof DESIGNKITS_CUSTOMERS
| typeof TEMPLATES_CUSTOMERS;
}) {
return (
<Section cozy bg="transparent">
<Box sx={{ minHeight: { xs: 236, sm: 144, md: 52 } }}>
<CompaniesGrid data={companies} />
</Box>
<Typography
variant="body2"
sx={{
textAlign: 'center',
color: 'text.secondary',
mt: 4,
mx: 'auto',
maxWidth: 400,
// hard-coded to reduce CLS (layout shift)
minHeight: 42,
}}
>
The world&apos;s best product teams trust MUI to deliver an unrivaled experience for both
developers and users.
</Typography>
</Section>
);
}

View File

@@ -0,0 +1,90 @@
import * as React from 'react';
import Box, { BoxProps } from '@mui/material/Box';
import Fade from '@mui/material/Fade';
import NoSsr from '@mui/material/NoSsr';
import Frame from 'docs/src/components/action/Frame';
export function ShowcaseCodeWrapper({
children,
clip,
hasDesignToggle,
maxHeight,
sx,
}: {
clip?: boolean;
children: React.ReactNode;
hasDesignToggle?: boolean;
maxHeight: number | string;
sx?: BoxProps['sx'];
}) {
return (
<Box
sx={{
p: 2,
pt: hasDesignToggle ? 7 : 2,
maxHeight: { xs: 'auto', sm: maxHeight },
position: 'relative',
display: 'flex',
overflow: clip ? 'clip' : 'auto',
flexGrow: 1,
'&::-webkit-scrollbar': {
display: 'none',
},
...sx,
}}
>
{children}
</Box>
);
}
export default function ShowcaseContainer({
code,
noPadding,
preview,
sx,
}: {
code?: React.ReactNode;
noPadding?: boolean;
preview?: React.ReactNode;
sx?: BoxProps['sx'];
}) {
return (
<Fade in timeout={700}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
height: '100%',
'& > div:first-of-type': {
borderTopLeftRadius: '12px',
borderTopRightRadius: '12px',
},
'& > div:last-of-type': {
borderBottomLeftRadius: '12px',
borderBottomRightRadius: '12px',
},
...sx,
}}
>
<Frame.Demo
sx={{
p: noPadding ? 0 : 2,
minHeight: 220,
position: 'relative',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{preview}
</Frame.Demo>
{code ? (
<Frame.Info data-mui-color-scheme="dark" sx={{ p: 0 }}>
<NoSsr>{code}</NoSsr>
</Frame.Info>
) : null}
</Box>
</Fade>
);
}

View File

@@ -0,0 +1,67 @@
import Avatar from '@mui/material/Avatar';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import { Link } from '@mui/docs/Link';
export default function SponsorCard(props: {
item: {
src: string;
srcDark?: string;
srcSet?: string;
name: string;
description: string;
href: string;
};
inView?: boolean;
logoSize?: number | string;
}) {
const { item, inView = false, logoSize = 40 } = props;
// Keep it under two rows maximum.
if (item.description.length > 50 && logoSize === 40) {
throw new Error(
`${item.name}'s description is too long (${item.description.length} characters). It must fit into two line, so under 50 characters.`,
);
}
return (
<Paper
component={Link}
noLinkStyle
data-ga-event-category="sponsor"
data-ga-event-action="homepage"
data-ga-event-label={new URL(item.href).hostname}
href={item.href}
target="_blank"
rel="sponsored noopener"
variant="outlined"
sx={{
p: 2,
display: 'flex',
gap: 2,
height: '100%',
}}
>
<Avatar
{...(inView && { src: item.src, srcSet: item.srcSet, alt: `${item.name} logo` })}
sx={[
{ borderRadius: '4px', width: logoSize, height: logoSize },
(theme) =>
item.srcDark
? theme.applyDarkStyles({
content: `url(${item.srcDark})`,
})
: null,
]}
slotProps={{ img: { loading: 'lazy' } }}
/>
<div>
<Typography variant="body2" sx={{ fontWeight: 'semiBold', mb: '2px' }}>
{item.name}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{item.description}
</Typography>
</div>
</Paper>
);
}

View File

@@ -0,0 +1,25 @@
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import DiamondSponsors from 'docs/src/components/home/DiamondSponsors';
import GoldSponsors from 'docs/src/components/home/GoldSponsors';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
export default function Sponsors() {
return (
<Section cozy>
<SectionHeadline
id="sponsors"
overline="Sponsors"
title={
<Typography variant="h2" sx={{ my: 1 }}>
<GradientText>You</GradientText> make this possible
</Typography>
}
description="The development of these open-source tools is accelerated by our generous sponsors."
/>
<DiamondSponsors />
<GoldSponsors />
</Section>
);
}

View File

@@ -0,0 +1,25 @@
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import GradientText from 'docs/src/components/typography/GradientText';
import GetStartedButtons from 'docs/src/components/home/GetStartedButtons';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
export default function StartToday() {
return (
<Box
sx={{ display: 'flex', flexDirection: 'column', alignItems: { xs: 'auto', sm: 'center' } }}
>
<SectionHeadline
alwaysCenter
overline="Start now"
title={
<Typography variant="h2">
Ship your next project <GradientText>faster</GradientText>
</Typography>
}
description="Find out why MUI's tools are trusted by thousands of open-source developers and teams around the world."
/>
<GetStartedButtons primaryLabel="Discover the Core libraries" primaryUrl="/core/" />
</Box>
);
}

View File

@@ -0,0 +1,297 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box, { BoxProps } from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import LaunchRounded from '@mui/icons-material/LaunchRounded';
import Slide from 'docs/src/components/animation/Slide';
import FadeDelay from 'docs/src/components/animation/FadeDelay';
const ratio = 900 / 494;
// 'transparent' is interpreted as transparent black in Safari
// See https://css-tricks.com/thing-know-gradients-transparent-black/
const transparent = 'rgba(255,255,255,0)';
const Image = styled('img')(({ theme }) => ({
display: 'block',
width: 200,
height: 200 / ratio,
[theme.breakpoints.up('sm')]: {
width: 300,
height: 300 / ratio,
},
[theme.breakpoints.up('md')]: {
width: 450,
height: 450 / ratio,
},
border: '4px solid',
borderColor: (theme.vars || theme).palette.grey[400],
borderRadius: (theme.vars || theme).shape.borderRadius,
objectFit: 'cover',
objectPosition: 'top',
boxShadow: '0px 4px 20px rgba(61, 71, 82, 0.25)',
...theme.applyDarkStyles({
borderColor: (theme.vars || theme).palette.grey[800],
boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.6)',
}),
}));
const Anchor = styled('a')({
display: 'inline-block',
position: 'relative',
transition: '0.3s',
'&:hover, &:focus': {
'& > div': {
opacity: 1,
},
},
});
const linkMapping = {
minimal: 'https://mui.com/store/items/minimal-dashboard/',
theFront: 'https://mui.com/store/items/the-front-landing-page/',
miro: 'https://mui.com/store/items/mira-pro-react-material-admin-dashboard/',
devias: 'https://mui.com/store/items/devias-kit-pro/',
berry: 'https://mui.com/store/items/berry-react-material-admin/',
webbee: 'https://mui.com/store/items/webbee-landing-page/',
};
const brands = Object.keys(linkMapping) as Array<keyof typeof linkMapping>;
type TemplateBrand = (typeof brands)[number];
const StoreTemplateLink = React.forwardRef<
HTMLAnchorElement,
React.PropsWithChildren<{
brand: TemplateBrand;
}>
>(function StoreTemplateLink({ brand, ...props }, ref) {
return (
<Anchor
ref={ref}
aria-label="Go to MUI Store"
href={`${linkMapping[brand]}?utm_source=marketing&utm_medium=referral&utm_campaign=home-cta`}
target="_blank"
{...props}
>
{props.children}
<Box
sx={{
transition: '0.3s',
borderRadius: 1,
position: 'absolute',
width: '100%',
height: '100%',
opacity: 0,
top: 0,
left: 0,
bgcolor: (theme) => alpha(theme.palette.primaryDark[500], 0.8),
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography sx={{ fontWeight: 'bold' }}>Go to store</Typography>
<LaunchRounded fontSize="small" sx={{ ml: 1 }} />
</Box>
</Anchor>
);
});
const StoreTemplateImage = React.forwardRef<
HTMLImageElement,
{ brand: TemplateBrand } & Omit<React.JSX.IntrinsicElements['img'], 'ref'>
>(function StoreTemplateImage({ brand, ...props }, ref) {
return (
<Image
ref={ref}
src={`/static/branding/store-templates/template-${
Object.keys(linkMapping).indexOf(brand) + 1
}light.jpg`}
alt=""
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/store-templates/template-${
Object.keys(linkMapping).indexOf(brand) + 1
}dark.jpg)`,
})
}
{...props}
/>
);
});
export function PrefetchStoreTemplateImages() {
function makeImg(mode: string, num: number) {
return {
loading: 'lazy' as const,
width: '900',
height: '494',
src: `/static/branding/store-templates/template-${num}${mode}.jpg`,
};
}
return (
<Box
sx={{
width: 0,
height: 0,
position: 'fixed',
zIndex: -1,
top: -1000,
'& > img': {
position: 'absolute',
},
}}
>
{[...Array(6)].map((_, index) => (
<React.Fragment key={index}>
<img alt="" {...makeImg('light', index + 1)} />
<img alt="" {...makeImg('dark', index + 1)} />
</React.Fragment>
))}
</Box>
);
}
const defaultSlideDown = {
'0%': {
transform: 'translateY(-300px)',
},
'100%': {
transform: 'translateY(-60px)',
},
};
export function StoreTemplatesSet1({
keyframes = defaultSlideDown,
disableLink,
...props
}: { disableLink?: boolean; keyframes?: Record<string, object> } & BoxProps) {
function renderTemplate(brand: TemplateBrand) {
if (disableLink) {
return <StoreTemplateImage brand={brand} />;
}
return (
<StoreTemplateLink brand={brand}>
<StoreTemplateImage brand={brand} />
</StoreTemplateLink>
);
}
return (
<Slide animationName="template-slidedown" {...props} keyframes={keyframes}>
<FadeDelay delay={400}>{renderTemplate(brands[4])}</FadeDelay>
<FadeDelay delay={200}>{renderTemplate(brands[2])}</FadeDelay>
<FadeDelay delay={0}>{renderTemplate(brands[0])}</FadeDelay>
</Slide>
);
}
const defaultSlideUp = {
'0%': {
transform: 'translateY(150px)',
},
'100%': {
transform: 'translateY(-20px)',
},
};
export function StoreTemplatesSet2({
keyframes = defaultSlideUp,
disableLink,
...props
}: { disableLink?: boolean; keyframes?: Record<string, object> } & BoxProps) {
function renderTemplate(brand: TemplateBrand) {
if (disableLink) {
return <StoreTemplateImage brand={brand} />;
}
return (
<StoreTemplateLink brand={brand}>
<StoreTemplateImage brand={brand} />
</StoreTemplateLink>
);
}
return (
<Slide animationName="template-slidedup" {...props} keyframes={keyframes}>
<FadeDelay delay={100}>{renderTemplate(brands[1])}</FadeDelay>
<FadeDelay delay={300}>{renderTemplate(brands[3])}</FadeDelay>
<FadeDelay delay={500}>{renderTemplate(brands[5])}</FadeDelay>
</Slide>
);
}
export default function StoreTemplatesBanner() {
return (
<Box
sx={{
mx: { xs: -2, sm: -3, md: 0 },
my: { md: -18 },
height: { xs: 300, sm: 360, md: 'calc(100% + 320px)' },
overflow: 'hidden',
position: 'relative',
width: { xs: '100vw', md: '50vw' },
}}
>
<Box
sx={(theme) => ({
display: { xs: 'block', md: 'none' },
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
pointerEvents: 'none',
zIndex: 2,
...theme.applyDarkStyles({
background: `linear-gradient(to bottom, ${
(theme.vars || theme).palette.primaryDark[900]
} 0%, ${alpha(theme.palette.primaryDark[900], 0)} 30%, ${alpha(
theme.palette.primaryDark[900],
0,
)} 70%, ${(theme.vars || theme).palette.primaryDark[900]} 100%)`,
}),
})}
/>
<Box
sx={{
// need perspective on this wrapper to work in Safari
height: '100%',
position: 'relative',
perspective: '1000px',
}}
>
<Box
sx={{
left: { xs: '45%', md: '40%' },
position: 'absolute',
zIndex: -1,
display: 'flex',
transform: 'translateX(-40%) rotateZ(-30deg) rotateX(8deg) rotateY(8deg)',
transformOrigin: 'center center',
}}
>
<StoreTemplatesSet1 />
<StoreTemplatesSet2 sx={{ ml: { xs: 2, sm: 4, md: 8 } }} />
</Box>
</Box>
<Box
sx={(theme) => ({
display: { xs: 'none', md: 'block' },
position: 'absolute',
top: 0,
left: 0,
width: 400,
height: '150%',
pointerEvents: 'none',
zIndex: 10,
background: `linear-gradient(to right, ${
(theme.vars || theme).palette.primary[50]
}, ${transparent})`,
...theme.applyDarkStyles({
background: `linear-gradient(to right, ${
(theme.vars || theme).palette.primaryDark[900]
}, ${alpha(theme.palette.primary[900], 0)})`,
}),
})}
/>
</Box>
);
}

View File

@@ -0,0 +1,34 @@
import dynamic from 'next/dynamic';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
const UserFeedbacks = dynamic(() => import('./UserFeedbacks'));
export default function Testimonials() {
return (
<Box
data-mui-color-scheme="dark"
sx={(theme) => ({
background: `linear-gradient(180deg, ${alpha(theme.palette.primaryDark[800], 0.8)}2%, ${
theme.palette.primaryDark[900]
} 80%), ${theme.palette.primaryDark[900]}`,
})}
>
<Section bg="transparent" cozy>
<SectionHeadline
overline="Join the community"
title={
<Typography variant="h2" component="h2">
Supported by thousands of <GradientText>developers and designers</GradientText>
</Typography>
}
/>
<UserFeedbacks />
</Section>
</Box>
);
}

View File

@@ -0,0 +1,185 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import MuiStatistics from 'docs/src/components/home/MuiStatistics';
const TESTIMONIALS = [
{
quote:
'"We\'ve relied on Material UI really heavily. I override a lot of default styles to try and make things our own, but the time we save with complex components like the Autocomplete and the Data Grid are so worth it. Every other library I try has 80% of what I\'m looking for when it comes to complex use cases, Material UI has it all under one roof which is a huge help for our small team."',
profile: {
avatarSrc: 'https://avatars.githubusercontent.com/u/21114044?s=58',
avatarSrcSet: 'https://avatars.githubusercontent.com/u/21114044?s=116 2x',
name: 'Kyle Gill',
role: 'Engineer & Designer',
company: (
<img
src="/static/branding/companies/particl-dark.svg"
width="90"
height="16"
alt="Particl logo"
loading="lazy"
/>
),
},
},
{
quote:
'"Material UI looks great and lets us deliver fast, thanks to their solid API design and documentation - it\'s refreshing to use a component library where you get everything you need from their site rather than Stack Overflow. We think the upcoming version, with extra themes and customizability, will make Material UI even more of a game changer. We\'re extremely grateful to the team for the time and effort spent maintaining the project."',
profile: {
avatarSrc: 'https://avatars.githubusercontent.com/u/197016?s=58',
avatarSrcSet: 'https://avatars.githubusercontent.com/u/197016?s=116 2x',
name: 'Jean-Laurent de Morlhon',
role: 'VP of Engineering',
company: (
<img
src="/static/branding/companies/docker-blue.svg"
width="81"
height="21"
alt="Docker logo"
loading="lazy"
/>
),
},
},
{
quote:
'"Material UI offers a wide variety of high quality components that have allowed us to ship features faster. It has been used by more than a hundred engineers in our organization. What\'s more, Material UI\'s well architected customization system has allowed us to differentiate ourselves in the marketplace."',
profile: {
avatarSrc: 'https://avatars.githubusercontent.com/u/28296253?s=58',
avatarSrcSet: 'https://avatars.githubusercontent.com/u/28296253?s=116 2x',
name: 'Joona Rahko',
role: 'Staff Software Engineer',
company: (
<img
src="/static/branding/companies/unity-blue.svg"
width="56"
height="21"
alt="Unity logo"
loading="lazy"
/>
),
},
},
{
quote:
'"After much research on React component libraries, we decided to ditch our in-house library for Material UI, using its powerful customization system to implement our Design System. This simple move did a rare thing in engineering: it lowered our maintenance costs while enhancing both developer and customer experience. All of this was done without sacrificing the organization\'s branding and visual identity."',
profile: {
avatarSrc: 'https://avatars.githubusercontent.com/u/732422?s=58',
avatarSrcSet: 'https://avatars.githubusercontent.com/u/732422?s=116 2x',
name: 'Gustavo de Paula',
role: 'Specialist Software Engineer',
company: (
<img
src="/static/branding/companies/loggi-blue.svg"
width="61"
height="20"
alt="Loggi logo"
loading="lazy"
/>
),
},
},
];
function Feedback({
quote,
profile,
}: {
quote: string;
profile: {
avatarSrc: string;
avatarSrcSet: string;
name: string;
role: string;
company?: React.ReactElement<unknown>;
};
}) {
return (
<Box
sx={{
p: 3,
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
color: '#FFF',
}}
>
<Typography
sx={{
mb: 2.5,
lineHeight: 1.6,
color: 'grey.200',
fontSize: (theme) => theme.typography.pxToRem(15),
}}
>
{quote}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box
sx={(theme) => ({
p: 0.5,
border: '1px solid',
borderColor: 'primary.800',
bgcolor: alpha(theme.palette.primary[900], 0.5),
borderRadius: 99,
})}
>
<Avatar
srcSet={profile.avatarSrcSet}
src={profile.avatarSrc}
alt={`${profile.name}'s profile picture`}
slotProps={{ img: { loading: 'lazy' } }}
sx={{ width: 36, height: 36 }}
/>
</Box>
<div>
<Typography variant="body2" sx={{ fontWeight: 'semiBold', color: 'text.primary' }}>
{profile.name}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{profile.role}
</Typography>
</div>
<Box sx={{ ml: 'auto' }}>{profile.company}</Box>
</Box>
</Box>
);
}
export default function UserFeedbacks() {
return (
<Grid
container
sx={(theme) => ({
mt: 4,
backgroundColor: 'rgba(255,255,255,0.01)',
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
overflow: 'clip',
'> :nth-of-type(1)': { borderBottom: `1px solid ${theme.palette.primaryDark[700]}` },
'> :nth-of-type(2)': {
borderBottom: `1px solid ${theme.palette.primaryDark[700]}`,
borderRight: { xs: 0, sm: `1px solid ${theme.palette.primaryDark[700]}` },
},
'> :nth-of-type(3)': { borderBottom: `1px solid ${theme.palette.primaryDark[700]}` },
'> :nth-of-type(4)': {
borderRight: { xs: 0, sm: `1px solid ${theme.palette.primaryDark[700]}` },
borderBottom: { xs: `1px solid ${theme.palette.primaryDark[700]}`, sm: 0 },
},
})}
>
<MuiStatistics />
{TESTIMONIALS.map((item) => (
<Grid key={item.profile.name} size={{ xs: 12, sm: 6 }}>
<Feedback {...item} />
</Grid>
))}
</Grid>
);
}

View File

@@ -0,0 +1,60 @@
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import InvertColorsRoundedIcon from '@mui/icons-material/InvertColorsRounded';
import HandymanRoundedIcon from '@mui/icons-material/HandymanRounded';
import ArticleRoundedIcon from '@mui/icons-material/ArticleRounded';
import AccessibilityNewRounded from '@mui/icons-material/AccessibilityNewRounded';
import GradientText from 'docs/src/components/typography/GradientText';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import { InfoCard } from '@mui/docs/InfoCard';
const content = [
{
icon: <InvertColorsRoundedIcon fontSize="small" color="primary" />,
title: 'Timeless aesthetics',
description:
"Build beautiful UIs with ease. Start with Google's Material Design, or create your own sophisticated theme.",
},
{
icon: <HandymanRoundedIcon fontSize="small" color="primary" />,
title: 'Intuitive customization',
description:
'Our components are as flexible as they are powerful. You always have full control over how they look and behave.',
},
{
icon: <ArticleRoundedIcon fontSize="small" color="primary" />,
title: 'Unrivaled documentation',
description:
'The answer to your problem can be found in our docs. How can we be so sure? Because our docs boast over 2,000 contributors.',
},
{
icon: <AccessibilityNewRounded fontSize="small" color="primary" />,
title: 'Dedicated to accessibility',
description:
"We believe in building for everyone. That's why accessibility is a high priority with every new feature we ship.",
},
];
export default function ValueProposition() {
return (
<Section>
<SectionHeadline
overline="Why build with MUI?"
title={
<Typography variant="h2" sx={{ mt: 1, mb: { xs: 2, sm: 4 } }}>
A <GradientText>delightful experience</GradientText> <br />
for you and your users
</Typography>
}
/>
<Grid container spacing={3}>
{content.map(({ icon, title, description }) => (
<Grid key={title} size={{ xs: 12, sm: 6, lg: 3 }}>
<InfoCard title={title} icon={icon} description={description} />
</Grid>
))}
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,153 @@
import { alpha } from '@mui/material/styles';
import GlobalStyles from '@mui/material/GlobalStyles';
export default function XGridGlobalStyles({
selector = 'body',
pro = false,
}: {
selector?: string;
pro?: boolean;
}) {
return (
<GlobalStyles
styles={(theme) => [
{
[selector]: {
'& .MuiDataGrid-root': {
fontSize: '0.75rem',
'--DataGrid-rowBorderColor': (theme.vars || theme).palette.grey[200],
// toolbar
// style GridToolbar
'& .MuiDataGrid-toolbar': {
flexShrink: 0,
padding: theme.spacing(0.5),
gap: theme.spacing(0.75),
minHeight: 'auto',
borderColor: (theme.vars || theme).palette.divider,
'& > .MuiIconButton-root, & > .MuiDataGrid-toolbarQuickFilter > .MuiIconButton-root':
{
flexShrink: 0,
border: '1px solid',
padding: theme.spacing(0.75),
borderColor: (theme.vars || theme).palette.divider,
'& svg': {
fontSize: '1.125rem',
},
},
'& .MuiDataGrid-toolbarDivider': {
display: 'none',
},
'& .MuiInputBase-input': {
padding: theme.spacing(0.75, 1),
},
},
'& .MuiCheckbox-root': {
color: (theme.vars || theme).palette.grey[600],
padding: theme.spacing(0.5),
'& > svg': {
fontSize: '1.2rem',
},
},
'& .MuiIconButton-root:not(.Mui-disabled)': {
color: (theme.vars || theme).palette.primary.main,
},
// table head elements
'& .MuiDataGrid-menuIcon svg': {
fontSize: '1rem',
},
'& .MuiDataGrid-columnSeparator': {
color: (theme.vars || theme).palette.grey[200],
'&.MuiDataGrid-columnSeparator--resizable:hover': {
color: (theme.vars || theme).palette.primary.main,
},
...(!pro && { display: 'none' }),
},
// -------------------------------
// table body elements
'& .MuiDataGrid-virtualScroller': {
backgroundColor: (theme.vars || theme).palette.grey[50],
},
'& .MuiDataGrid-editInputCell': {
fontSize: '0.75rem',
'& > input': {
padding: theme.spacing(0, 1),
},
},
'& .MuiDataGrid-cell--editing': {
'& .MuiSelect-root': {
'& .MuiListItemIcon-root': {
display: 'none',
},
'& .MuiTypography-root': {
fontSize: '0.75rem',
},
},
},
'& .MuiTablePagination-root': {
marginRight: theme.spacing(1),
'& .MuiIconButton-root': {
'&:not([disabled])': {
color: (theme.vars || theme).palette.primary.main,
borderColor: (theme.vars || theme).palette.grey[400],
},
borderRadius: (theme.vars || theme).shape.borderRadius,
padding: theme.spacing(0.5),
border: '1px solid',
borderColor: (theme.vars || theme).palette.grey[200],
'&:last-of-type': {
marginLeft: theme.spacing(1),
},
'& > svg': {
fontSize: '1rem',
},
},
},
},
'& .MuiDataGrid-gridMenuList': {
boxShadow: '0px 4px 20px rgb(61 71 82 / 25%)',
borderRadius: '10px',
'& .MuiMenuItem-root': {
fontSize: '0.75rem',
},
},
},
},
theme.applyDarkStyles({
[selector]: {
'& .MuiDataGrid-root': {
'--DataGrid-rowBorderColor': alpha(theme.palette.primaryDark[500], 0.5),
'& .MuiDataGrid-toolbar': {
'& > button': {
borderColor: (theme.vars || theme).palette.divider,
},
},
'& .MuiCheckbox-root': {
color: (theme.vars || theme).palette.primary[300],
},
'& .MuiIconButton-root:not(.Mui-disabled)': {
color: (theme.vars || theme).palette.primary[300],
},
'& .MuiDataGrid-columnSeparator': {
color: (theme.vars || theme).palette.primaryDark[400],
},
// -------------------------------
// table body elements
'& .MuiDataGrid-virtualScroller': {
backgroundColor: (theme.vars || theme).palette.primaryDark[900],
},
'& .MuiTablePagination-root': {
'& .MuiIconButton-root': {
'&:not([disabled])': {
color: (theme.vars || theme).palette.primaryDark[100],
borderColor: (theme.vars || theme).palette.primaryDark[400],
},
borderColor: (theme.vars || theme).palette.primaryDark[600],
},
},
},
},
}),
]}
/>
);
}

View File

@@ -0,0 +1,117 @@
import * as React from 'react';
import { styled, Theme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import { SxProps } from '@mui/system';
import { ThemeOptionsContext } from 'docs/src/modules/components/ThemeContext';
export type IconImageProps = {
name:
| 'product-core'
| 'product-advanced'
| 'product-toolpad'
| 'product-templates'
| 'product-designkits'
| 'pricing/x-plan-pro'
| 'pricing/x-plan-premium'
| 'pricing/x-plan-community'
| 'pricing/yes'
| 'pricing/no'
| 'pricing/time'
| 'companies/spotify'
| 'companies/amazon'
| 'companies/nasa'
| 'companies/netflix'
| 'companies/unity'
| 'companies/shutterstock'
| 'companies/southwest'
| 'companies/siemens'
| 'companies/deloitte'
| 'companies/apple'
| 'companies/twitter'
| 'companies/salesforce'
| 'companies/verizon'
| 'companies/atandt'
| 'companies/patreon'
| 'companies/ebay'
| 'companies/samsung'
| 'companies/volvo'
| 'companies/tesla'
| string;
height?: number;
mode?: '' | 'light' | 'dark';
sx?: SxProps<Theme>;
width?: number;
} & Omit<React.JSX.IntrinsicElements['img'], 'ref'>;
const Img = styled('img')({ display: 'inline-block', verticalAlign: 'bottom' });
let neverHydrated = true;
export default function IconImage(props: IconImageProps) {
const { height: heightProp, name, width: widthProp, mode: modeProp, ...other } = props;
const themeOptions = React.useContext(ThemeOptionsContext);
const [firstRender, setFirstRender] = React.useState(true);
React.useEffect(() => {
setFirstRender(false);
neverHydrated = false;
}, []);
let defaultWidth;
let defaultHeight;
const mode = modeProp ?? themeOptions.paletteMode;
if (name.startsWith('product-')) {
defaultWidth = 36;
defaultHeight = 36;
} else if (name.startsWith('pricing/x-plan-')) {
defaultWidth = 13;
defaultHeight = 15;
} else if (['pricing/yes', 'pricing/no', 'pricing/time'].includes(name)) {
defaultWidth = 18;
defaultHeight = 18;
}
const width = widthProp ?? defaultWidth;
const height = heightProp ?? defaultHeight;
// First time render with a theme depend image
if (firstRender && neverHydrated && mode !== '') {
if (other.loading === 'eager') {
return (
<React.Fragment>
<Img
className="only-light-mode-v2"
src={`/static/branding/${name}-light.svg`}
alt=""
width={width}
height={height}
{...other}
loading="lazy"
/>
<Img
className="only-dark-mode-v2"
src={`/static/branding/${name}-dark.svg`}
alt=""
width={width}
height={height}
{...other}
loading="lazy"
/>
</React.Fragment>
);
}
// Prevent hydration mismatch between the light and dark mode image source.
return <Box component="span" sx={{ width, height, display: 'inline-block' }} />;
}
return (
<Img
src={`/static/branding/${name}${mode ? `-${mode}` : ''}.svg`}
alt=""
loading="lazy"
width={width}
height={height}
{...other}
/>
);
}

View File

@@ -0,0 +1,77 @@
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import Button from '@mui/material/Button';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import { alpha } from '@mui/material/styles';
import { Link } from '@mui/docs/Link';
export default function EarlyBird() {
return (
<Container
sx={{
pt: 2,
pb: { xs: 2, sm: 4, md: 8 },
scrollMarginTop: 'calc(var(--MuiDocs-header-height) + 32px)',
}}
id="early-bird"
>
<Stack
sx={(theme) => ({
borderRadius: 1,
px: 2,
py: 3,
background: `linear-gradient(180deg, ${alpha(theme.palette.primary[50], 0.2)} 50%,
${(theme.vars || theme).palette.primary[50]} 100%)
`,
border: '1px solid',
borderColor: 'grey.100',
display: 'flex',
flexDirection: {
xs: 'column',
sm: 'row',
},
justifyContent: 'space-between',
alignItems: {
xs: 'flex-start',
sm: 'center',
},
...theme.applyDarkStyles({
background: `linear-gradient(180deg, ${alpha(theme.palette.primary[900], 0.4)} 50%,
${alpha(theme.palette.primary[800], 0.6)} 100%)
`,
borderColor: 'primaryDark.600',
}),
})}
>
<div>
<Typography sx={{ fontWeight: 'bold', mb: 0.5 }}>
🐦&nbsp;&nbsp;Early bird special!
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', maxWidth: 700 }}>
Buy now at a reduced price (~25% off), and get early access to MUI X Premium, with the
added opportunity to influence its development. The early bird special is available for
a limited time, so don&apos;t miss this opportunity!
</Typography>
</div>
<Button
component={Link}
noLinkStyle
href="https://mui.com/store/items/mui-x-premium/"
variant="contained"
fullWidth
endIcon={<KeyboardArrowRightRounded />}
sx={{
py: 1,
flexShrink: 0,
ml: { xs: 0, sm: 2 },
mt: { xs: 3, sm: 0 },
width: { xs: '100%', sm: '50%', md: 'fit-content' },
}}
>
Buy now
</Button>
</Stack>
</Container>
);
}

View File

@@ -0,0 +1,21 @@
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
export default function HeroPricing() {
return (
<Section cozy>
<SectionHeadline
alwaysCenter
overline="Pricing"
title={
<Typography variant="h2" component="h1">
Start using MUI&apos;s products <GradientText>for free!</GradientText>
</Typography>
}
description="Switch to a commercial plan to access advanced features & technical support."
/>
</Section>
);
}

View File

@@ -0,0 +1,55 @@
import * as React from 'react';
import Typography from '@mui/material/Typography';
import { usePrioritySupport } from 'docs/src/components/pricing/PrioritySupportContext';
export default function InfoPrioritySupport(props: {
value: React.ReactNode;
value2?: React.ReactNode;
metadata?: React.ReactNode;
metadata2?: React.ReactNode;
}) {
const { value, value2, metadata, metadata2 } = props;
const { prioritySupport } = usePrioritySupport();
return (
<React.Fragment>
{prioritySupport ? (
<React.Fragment>
<Typography variant="body2" sx={{ color: 'text.secondary', textAlign: 'center' }}>
{value}
</Typography>
<Typography
variant="caption"
sx={{
color: 'text.secondary',
fontWeight: 'normal',
display: 'block',
mt: 0.4,
textAlign: 'center',
}}
>
{metadata}
</Typography>
</React.Fragment>
) : (
<React.Fragment>
<Typography variant="body2" sx={{ color: 'text.secondary', textAlign: 'center' }}>
{value2}
</Typography>
<Typography
variant="caption"
sx={{
color: 'text.secondary',
fontWeight: 'normal',
display: 'block',
mt: 0.4,
textAlign: 'center',
}}
>
{metadata2}
</Typography>
</React.Fragment>
)}
</React.Fragment>
);
}

View File

@@ -0,0 +1,20 @@
import * as React from 'react';
const LicenseModel = React.createContext<any>({});
if (process.env.NODE_ENV !== 'production') {
LicenseModel.displayName = 'LicenseModel';
}
export function LicenseModelProvider(props: any) {
const [licenseModel, setLicenseModel] = React.useState<string>('annual');
const value = React.useMemo(
() => ({ licenseModel, setLicenseModel }),
[licenseModel, setLicenseModel],
);
return <LicenseModel.Provider value={value}>{props.children}</LicenseModel.Provider>;
}
export function useLicenseModel() {
return React.useContext(LicenseModel);
}

View File

@@ -0,0 +1,133 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Tooltip from '@mui/material/Tooltip';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import { useLicenseModel } from 'docs/src/components/pricing/LicenseModelContext';
const StyledTabs = styled(Tabs)(({ theme }) => ({
margin: '14px auto 4px',
padding: 2,
maxWidth: 170,
minHeight: 0,
overflow: 'visible',
borderRadius: 20,
border: '1px solid',
borderColor: (theme.vars || theme).palette.grey[100],
backgroundColor: (theme.vars || theme).palette.grey[50],
'&:has(.Mui-focusVisible)': {
outline: `3px solid ${alpha(theme.palette.primary[500], 0.5)}`,
outlineOffset: '2px',
},
'& .MuiTabs-scroller, & .MuiTab-root': {
// Override inline-style to see the box-shadow
overflow: 'visible!important',
},
'& span': {
zIndex: 1,
},
'& .MuiTab-root': {
padding: '4px 8px',
fontSize: theme.typography.pxToRem(13),
fontWeight: theme.typography.fontWeightSemiBold,
minWidth: 0,
minHeight: 0,
color: (theme.vars || theme).palette.text.tertiary,
borderRadius: 20,
zIndex: 2,
'&:hover': {
color: (theme.vars || theme).palette.text.primary,
},
'&.Mui-selected': {
color: (theme.vars || theme).palette.primary[600],
fontWeight: theme.typography.fontWeightSemiBold,
},
'&.Mui-focusVisible': {
outline: 'none',
},
},
'& .MuiTabs-indicator': {
backgroundColor: '#FFF',
border: '1px solid',
borderColor: (theme.vars || theme).palette.grey[200],
height: '100%',
borderRadius: 20,
zIndex: 0,
boxShadow: `0px 1px 2px ${(theme.vars || theme).palette.grey[200]}`,
},
...theme.applyDarkStyles({
borderColor: (theme.vars || theme).palette.primaryDark[700],
backgroundColor: (theme.vars || theme).palette.primaryDark[900],
'& .MuiTabs-indicator': {
height: '100%',
borderRadius: 20,
backgroundColor: alpha(theme.palette.primaryDark[600], 0.5),
borderColor: (theme.vars || theme).palette.primaryDark[600],
boxShadow: `0px 1px 4px ${(theme.vars || theme).palette.common.black}`,
},
'& .MuiTab-root': {
'&.Mui-selected': {
color: (theme.vars || theme).palette.primary[200],
},
},
}),
}));
const perpetualDescription =
'One-time purchase to use the current released versions forever. 12 months of updates included.';
const annualDescription =
'Upon expiration, your permission to use the Software in development ends. The license is perpetual in production.';
const tooltipProps = {
enterDelay: 400,
enterNextDelay: 50,
enterTouchDelay: 500,
placement: 'top' as const,
describeChild: true,
slotProps: {
tooltip: {
sx: {
fontSize: 12,
},
},
},
};
export default function LicenseModelSwitch() {
const { licenseModel, setLicenseModel } = useLicenseModel();
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setLicenseModel(newValue);
};
return (
<Box sx={{ display: 'flex' }}>
<StyledTabs
aria-label="license model"
selectionFollowsFocus
value={licenseModel}
onChange={handleChange}
>
<Tab
disableFocusRipple
value="perpetual"
label={
<Tooltip title={perpetualDescription} {...tooltipProps}>
<span>Perpetual</span>
</Tooltip>
}
/>
<Tab
disableFocusRipple
value="annual"
label={
<Tooltip title={annualDescription} {...tooltipProps}>
<span>Annual</span>
</Tooltip>
}
/>
</StyledTabs>
</Box>
);
}

View File

@@ -0,0 +1,659 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { alpha } from '@mui/material/styles';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Typography from '@mui/material/Typography';
import IconImage from 'docs/src/components/icon/IconImage';
import LicenseModelSwitch from 'docs/src/components/pricing/LicenseModelSwitch';
import { useLicenseModel } from 'docs/src/components/pricing/LicenseModelContext';
import PrioritySupportSwitch from 'docs/src/components/pricing/PrioritySupportSwitch';
import { usePrioritySupport } from 'docs/src/components/pricing/PrioritySupportContext';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import { Link } from '@mui/docs/Link';
import {
ProSupportIcon,
PremiumSupportIcon,
PrioritySupportIcon,
} from 'docs/src/components/pricing/SupportIcons';
export interface Feature {
text?: string;
highlight?: string;
text2?: string;
icon?: 'support' | 'check';
supportType?: 'community' | 'pro' | 'premium' | 'priority';
}
export type PlanName = 'community' | 'pro' | 'premium' | 'enterprise';
export const planInfo: Record<
PlanName,
{
iconName: string;
title: string;
description: string;
features: Feature[];
}
> = {
community: {
iconName: 'pricing/x-plan-community',
title: 'Community',
description: 'Get started with the industry-standard React UI library, MIT-licensed.',
features: [
{ text: '40+ free components', icon: 'check' },
{
icon: 'support',
supportType: 'community',
},
],
},
pro: {
iconName: 'pricing/x-plan-pro',
title: 'Pro',
description: 'Best for professional developers or startups building data-rich applications.',
features: [
{ text: 'All Community features and…', icon: 'check' },
{ text: 'MUI X', highlight: 'Pro', text2: 'access', icon: 'check' },
{ text: '10+', highlight: 'Pro', text2: 'features', icon: 'check' },
{ highlight: 'Pro', text2: 'support', icon: 'support', supportType: 'pro' },
],
},
premium: {
iconName: 'pricing/x-plan-premium',
title: 'Premium',
description:
'The most advanced features for data-rich applications along with standard support.',
features: [
{ text: 'All Pro', text2: 'features and…', icon: 'check' },
{ text: 'MUI X', highlight: 'Premium', text2: 'access', icon: 'check' },
{ text: '5+', highlight: 'Premium', text2: 'features', icon: 'check' },
{ highlight: 'Premium', text2: 'support', icon: 'support', supportType: 'premium' },
],
},
enterprise: {
iconName: 'pricing/x-plan-enterprise',
title: 'Enterprise',
description:
'All features of Premium coupled with enterprise-grade support and customer success.',
features: [
{ text: 'All Premium', text2: 'features and…', icon: 'check' },
{ text: 'Technical support for all libraries', icon: 'check' },
{ text: 'Guaranteed response time', icon: 'check' },
{ text: 'Pre-screening', icon: 'check' },
{ text: 'Issue escalation', icon: 'check' },
{ text: 'Customer Success Manager', icon: 'check' },
{ highlight: 'Priority', text2: 'support', icon: 'support', supportType: 'priority' },
],
},
};
const formatter = new Intl.NumberFormat('en-US');
function formatCurrency(value: number) {
return `$${formatter.format(value)}`;
}
interface PlanPriceProps {
plan: 'community' | 'pro' | 'premium' | 'enterprise';
}
export function PlanPrice(props: PlanPriceProps) {
const { plan } = props;
const { licenseModel } = useLicenseModel();
const annual = licenseModel === 'annual';
const planPriceMinHeight = 24;
const { prioritySupport } = usePrioritySupport();
if (plan === 'community') {
return (
<React.Fragment>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', mt: 2 }}>
<Typography
variant="h3"
component="div"
sx={{ fontWeight: 'bold', color: 'success.600' }}
>
$0
</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
mb: 1.5,
minHeight: planPriceMinHeight,
}}
>
<Typography
variant="body2"
sx={{ color: 'text.secondary', textAlign: 'center', minHeight: 38 }}
>
Free forever!
</Typography>
</Box>
<Button
component={Link}
noLinkStyle
href="/material-ui/getting-started/usage/"
variant="outlined"
fullWidth
endIcon={<KeyboardArrowRightRounded />}
sx={{ py: 1, mt: 2 }}
>
Get started
</Button>
</React.Fragment>
);
}
const priceUnit = annual ? '/ year / dev' : '/ dev';
const getPriceExplanation = (displayedValue: number) => {
if (annual) {
return `Equivalent to $${displayedValue} / month / dev`;
}
return '';
};
const perpetualMultiplier = 3;
if (plan === 'pro') {
const annualValue = 180;
const perpetualValue = annualValue * perpetualMultiplier;
const monthlyValueForAnnual = annualValue / 12;
const mainDisplayValue = annual ? annualValue : perpetualValue;
const priceExplanation = getPriceExplanation(annual ? monthlyValueForAnnual : perpetualValue);
return (
<Box
sx={{ display: 'flex', alignItems: 'center', flex: '1 1 auto', flexDirection: 'column' }}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', mt: 2 }}>
<Typography
variant="h3"
component="div"
sx={{ fontWeight: 'bold', color: 'primary.main' }}
>
{formatCurrency(mainDisplayValue)}
</Typography>
<Box sx={{ width: 5 }} />
<Typography variant="body2" sx={{ color: 'text.secondary', mt: '3px' }}>
{priceUnit}
</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
mb: 1.5,
minHeight: planPriceMinHeight,
}}
>
{
<Typography
variant="body2"
sx={{
color: 'text.secondary',
textAlign: 'center',
fontSize: '0.8125rem',
minHeight: 38,
}}
>
{priceExplanation}
</Typography>
}
</Box>
<Button
component={Link}
noLinkStyle
href={
licenseModel === 'annual'
? 'https://mui.com/store/items/mui-x-pro/'
: 'https://mui.com/store/items/mui-x-pro-perpetual/'
}
variant="contained"
endIcon={<KeyboardArrowRightRounded />}
sx={{ py: 1, width: '100%', mt: 2 }}
>
Buy now
</Button>
</Box>
);
}
if (plan === 'premium') {
const premiumAnnualValue = 588;
const premiumPerpetualValue = premiumAnnualValue * perpetualMultiplier;
const premiumMonthlyValueForAnnual = premiumAnnualValue / 12;
const premiumAnnualValueWithPrioritySupport = premiumAnnualValue + 399;
const premiumPerpetualValueWithPrioritySupport = premiumPerpetualValue + 399;
const premiumMonthlyValueForAnnualWithPrioritySupport = 82; // premiumAnnualValueWithPrioritySupport / 12;
const priceExplanation = getPriceExplanation(
prioritySupport
? premiumMonthlyValueForAnnualWithPrioritySupport
: premiumMonthlyValueForAnnual,
);
let premiumDisplayedValue: number = premiumAnnualValue;
if (annual && prioritySupport) {
premiumDisplayedValue = premiumAnnualValueWithPrioritySupport;
} else if (!annual && prioritySupport) {
premiumDisplayedValue = premiumPerpetualValueWithPrioritySupport;
} else if (annual && !prioritySupport) {
premiumDisplayedValue = premiumAnnualValue;
} else if (!annual && !prioritySupport) {
premiumDisplayedValue = premiumPerpetualValue;
}
return (
<Box
sx={{ display: 'flex', alignItems: 'center', flex: '1 1 auto', flexDirection: 'column' }}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mt: 2,
}}
>
<Typography
variant="h3"
component="div"
sx={{ fontWeight: 'bold', color: 'primary.main' }}
>
{formatCurrency(premiumDisplayedValue)}
</Typography>
<Box sx={{ width: 5 }} />
<Typography variant="body2" sx={{ color: 'text.secondary', mt: '3px' }}>
{priceUnit}
</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
mb: 1.5,
minHeight: planPriceMinHeight,
}}
>
{
<Typography
variant="body2"
sx={{
color: 'text.secondary',
textAlign: 'center',
fontSize: '0.8125rem',
minHeight: 38,
}}
>
{priceExplanation}
</Typography>
}
</Box>
<Button
component={Link}
noLinkStyle
href={getHref(annual, prioritySupport)}
variant="contained"
fullWidth
endIcon={<KeyboardArrowRightRounded />}
sx={{ py: 1, mt: 2, mb: 2 }}
>
Buy now
</Button>
<PrioritySupportSwitch />
</Box>
);
}
// else enterprise
return (
<Box sx={{ display: 'flex', alignItems: 'center', flex: '1 1 auto', flexDirection: 'column' }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'center',
mt: 2,
}}
>
<Typography
variant="h5"
component="div"
sx={{
fontWeight: 'bold',
fontSize: '1.40rem',
color: 'primary.main',
textAlign: 'center',
mt: 1,
}}
>
Custom pricing
</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
mb: 1.5,
minHeight: planPriceMinHeight,
}}
>
<Typography
sx={{
color: 'text.secondary',
textAlign: 'center',
fontSize: '0.8125rem',
minHeight: 38,
}}
>
Got a big team? Request a personalized quote!
</Typography>
</Box>
<Button
component={Link}
noLinkStyle
href="mailto:sales@mui.com"
variant="contained"
fullWidth
endIcon={<KeyboardArrowRightRounded />}
sx={{ py: 1, width: '100%', mt: 2 }}
>
Contact Sales
</Button>
</Box>
);
}
function getHref(annual: boolean, prioritySupport: boolean): string {
if (annual && prioritySupport) {
return 'https://mui.com/store/items/mui-x-premium/?addons=mui-x-priority-support';
}
if (!annual && prioritySupport) {
return 'https://mui.com/store/items/mui-x-premium-perpetual/?addons=mui-x-priority-support';
}
if (annual && !prioritySupport) {
return 'https://mui.com/store/items/mui-x-premium/';
}
return 'https://mui.com/store/items/mui-x-premium-perpetual/';
}
export function FeatureItem({ feature, idPrefix }: { feature: Feature; idPrefix?: string }) {
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
pl: feature.icon === 'check' ? 0.4 : null,
}}
>
{feature.icon === 'check' && (
<IconImage name="pricing/yes" sx={{ fontSize: 20, color: 'primary.main' }} />
)}
{feature.icon === 'support' && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 0.6,
}}
>
{(() => {
if (feature.supportType === 'pro') {
return <ProSupportIcon idPrefix={idPrefix} />;
}
if (feature.supportType === 'premium') {
return <PremiumSupportIcon idPrefix={idPrefix} />;
}
if (feature.supportType === 'priority') {
return <PrioritySupportIcon idPrefix={idPrefix} />;
}
return null;
})()}
</Box>
)}
<Typography
component="span"
variant="body2"
sx={{
display: 'flex',
alignItems: 'center',
gap: 0.5,
}}
>
{feature.text}
{feature.highlight && (
<Typography
variant="body2"
component="span"
sx={{
color: 'primary.main',
fontWeight: 500,
}}
>
{feature.highlight}
</Typography>
)}
{feature.text2 && ` ${feature.text2}`}
</Typography>
</Box>
);
}
export function PlanNameDisplay({
plan,
disableDescription = true,
}: {
plan: keyof typeof planInfo;
disableDescription?: boolean;
}) {
const { title, iconName, description } = planInfo[plan];
return (
<React.Fragment>
<Typography
variant="body2"
sx={{
fontWeight: 'bold',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
pr: 0.5,
}}
>
<IconImage name={iconName} mode="" loading="eager" sx={{ mr: 1 }} /> {title}
</Typography>
{!disableDescription && (
<Typography
variant="body2"
sx={{
color: 'text.secondary',
display: 'flex',
textAlign: 'center',
justifyContent: 'center',
alignItems: 'baseline',
mt: 1,
minHeight: 96,
height: 48,
lineHeight: '24px',
}}
>
{description}
</Typography>
)}
</React.Fragment>
);
}
export default function PricingCards() {
return (
<React.Fragment>
<LicenseModelSwitch />
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
mt: 3,
mb: 8,
gap: 2,
mx: 'auto',
maxWidth: '100%',
}}
>
<Box
sx={{
display: 'flex',
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
flexDirection: 'column',
gap: 3,
py: 3,
px: 2,
flex: '1 1 0px',
}}
>
<Box sx={{ height: 'fit-content' }}>
<PlanNameDisplay plan="community" disableDescription={false} />
<PlanPrice plan="community" />
</Box>
<Divider />
<Box textAlign="left">
{planInfo.community.features.map((feature, index) => (
<Box
key={index}
sx={{
mb: 2,
'&:last-child': {
mb: 0,
},
}}
>
<FeatureItem feature={feature} />
</Box>
))}
</Box>
</Box>
<Box
sx={{
display: 'flex',
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
flexDirection: 'column',
gap: 3,
py: 3,
px: 2,
flex: '1 1 0px',
}}
>
<Box sx={{ height: 'fit-content' }}>
<PlanNameDisplay plan="pro" disableDescription={false} />
<PlanPrice plan="pro" />
</Box>
<Divider />
<Box textAlign="left">
{planInfo.pro.features.map((feature, index) => (
<Box
key={index}
sx={{
mb: 2,
'&:last-child': {
mb: 0,
},
}}
>
<FeatureItem feature={feature} />
</Box>
))}
</Box>
</Box>
<Box
sx={(theme) => ({
display: 'flex',
border: '1px solid',
borderColor: 'primary.200',
borderRadius: 1,
flexDirection: 'column',
gap: 3,
py: 3,
px: 2,
flex: '1 1 0px',
background: `${(theme.vars || theme).palette.gradients.linearSubtle}`,
boxShadow: '0px 2px 12px 0px rgba(234, 237, 241, 0.3) inset',
...theme.applyDarkStyles({
borderColor: `${alpha(theme.palette.primary[700], 0.4)}`,
boxShadow: '0px 2px 12px 0px rgba(0, 0, 0, 0.25) inset',
}),
})}
>
<Box sx={{ height: 'fit-content' }}>
<PlanNameDisplay plan="premium" disableDescription={false} />
<PlanPrice plan="premium" />
</Box>
<Box textAlign="left">
{planInfo.premium.features.map((feature, index) => (
<Box
key={index}
sx={{
mb: 2,
'&:last-child': {
mb: 0,
},
}}
>
<FeatureItem feature={feature} />
</Box>
))}
</Box>
</Box>
<Box
sx={{
display: 'flex',
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
flexDirection: 'column',
gap: 3,
py: 3,
px: 2,
flex: '1 1 0px',
}}
>
<Box sx={{ height: 'fit-content' }}>
<PlanNameDisplay plan="enterprise" disableDescription={false} />
<PlanPrice plan="enterprise" />
</Box>
<Divider />
<Box textAlign="left">
{planInfo.enterprise.features.map((feature, index) => (
<Box
key={index}
sx={{
mb: 2,
'&:last-child': {
mb: 0,
},
}}
>
<FeatureItem feature={feature} />
</Box>
))}
</Box>
</Box>
</Box>
</React.Fragment>
);
}

View File

@@ -0,0 +1,331 @@
/* eslint-disable react/no-unescaped-entities */
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import MuiAccordion from '@mui/material/Accordion';
import MuiAccordionSummary from '@mui/material/AccordionSummary';
import MuiAccordionDetail from '@mui/material/AccordionDetails';
import KeyboardArrowDownRounded from '@mui/icons-material/KeyboardArrowDownRounded';
import Section from 'docs/src/layouts/Section';
const faqData = [
{
summary: 'How do I know if I need to buy a license?',
detail: (
<React.Fragment>
If you are in doubt, check the license file of the npm package you're installing. For
instance <Link href="https://unpkg.com/@mui/x-data-grid/LICENSE">@mui/x-data-grid</Link> is
an MIT License (free) while{' '}
<Link href="https://unpkg.com/@mui/x-data-grid-pro/LICENSE">@mui/x-data-grid-pro</Link> is a
Commercial License.
</React.Fragment>
),
},
{
summary: 'How many developer licenses do I need?',
detail: (
<React.Fragment>
The number of licenses purchased must correspond to the number of concurrent developers
contributing changes to the front-end code of projects that use MUI X Pro or Premium.
<br />
<br />
<b>Example 1.</b> Company 'A' is developing an application named 'AppA'. The app needs to
render 10k rows of data in a table and allow users to group, filter, and sort. The dev team
adds MUI X Pro to the project to satisfy this requirement. 5 front-end and 10 back-end
developers are working on 'AppA'. Only 1 developer is tasked with configuring and modifying
the data grid. Only the front-end developers are contributing code to the front-end so
Company 'A' purchases 5 licenses.
<br />
<br />
<b>Example 2.</b> A UI development team at Company 'B' creates its own UI library for
internal development and includes MUI X Pro as a component. The team working on 'AppA' uses
the new library and so does the team working on 'AppB'. 'AppA' has 5 front-end developers
and 'AppB' has 3. There are 2 front-end developers on the UI development team. Company 'B'
purchases 10 licenses.
<br />
<br />
<Link
target="_blank"
rel="noopener"
href="https://mui.com/legal/mui-x-eula/#required-quantity-of-licenses"
>
The clause in the EULA.
</Link>
</React.Fragment>
),
},
{
summary: 'Am I allowed to use the product after the update entitlement expires?',
detail: (
<React.Fragment>
<strong>Yes.</strong> You can continue to use the product in production environments after
the entitlement expires. But you will need to keep your subscription active to continue
development, update for new features, or gain access to technical support.
<br />
<br />
To renew your license, please <Link href="mailto:sales@mui.com">contact sales</Link>.
</React.Fragment>
),
},
{
summary: 'How to remove the "unlicensed" watermark?',
detail: (
<React.Fragment>
After you purchase a license, you'll receive a license key by email. Once you have the
license key, you need to follow the{' '}
<Link href="/x/introduction/licensing/#license-key-installation">instructions</Link>{' '}
necessary to set it up.
</React.Fragment>
),
},
{
summary: 'Do developers have to be named?',
detail: (
<React.Fragment>
<strong>No.</strong> We trust that you will not go over the number of licensed developers.
Developers moving on and off projects is expected occasionally, and the license can be
transferred between developers at that time.
</React.Fragment>
),
},
{
summary: 'What is the policy on redistributing the software?',
detail: (
<React.Fragment>
The commerial licenses are royalty-free. The licensed entity can use the components without
a sublicense in:
<ul>
<li>Solutions for internal company use</li>
<li>Hosted applications</li>
<li>Commercial solutions deployed for end-users</li>
</ul>
Based on the{' '}
<Link target="_blank" rel="noopener" href="https://mui.com/legal/mui-x-eula/#deployment">
'Deployment' section of the EULA
</Link>
, you can sublicense the software if it's made part of a larger work. The new licenses must
be in writing and substantially the same as these EULA.
<br />
<br />
<b>Example 1.</b> Agency 'A' is building two applications for companies 'B' and 'C'. Agency
'A' purchases four licenses for four developers. They build the applications and sublicense
the software to companies 'B' and 'C' without any extra fee. Company 'B' can deploy the
application built by the agency without modifying the sources. Company 'C' decides to
continue working on the application. They purchase one license per developer working on the
front end of the application.
<br />
<br />
There are only two limitations that require additional discussion with our sales team:
<ul>
<li>
A product that exposes the components in a form that allows for using them to build
applications, for example, in a CMS or a design-builder.
</li>
<li>
Modules/components that DO NOT add significant primary functionality. Example: a theme
for a set of components that is sold as a separate product and includes the MUI X Data
Grid Pro components. In such cases, we offer reseller arrangements so that everyone has
an incentive to enter into a relationship.
</li>
</ul>
If your desired use falls under any of the three categories listed above, please{' '}
<Link href="mailto:sales@mui.com">contact sales</Link>. We will be happy to discuss your
needs and see what we can do to accommodate your case.
</React.Fragment>
),
},
{
summary: 'Do you offer discounts to educational and non-profit organizations?',
detail: (
<React.Fragment>
Yes, we offer a 50% discount on all products licensed to students, instructors, non-profit,
and charity entities.
<br />
<br />
To qualify for this discount you need to send us a document clearly indicating that you are
a member of the respective institution. An email from your official account which bears your
signature is sufficient in most cases.
<br />
<br />
For more information on how to qualify for a discount, please contact sales.
</React.Fragment>
),
},
{
summary: 'Why must we license developers not using the software directly?',
detail: (
<React.Fragment>
Our pricing model requires all developers working on a project using MUI X Pro or Premium to
be licensed. This is intended to make it easier for you and your team to know if the right
number of developers are licensed.
<br />
<br />
Our licensing model also requires developers indirectly using MUI X Pro or Premium (e.g.
through a wrapper library) to be licensed.
<br />
<br />
The price point per developer is adjusted to be lower than if only direct use needed a
license.{' '}
<Link
target="_blank"
rel="noopener"
href="https://mui.com/legal/mui-x-eula/#required-quantity-of-licenses"
>
The relevant EULA clause.
</Link>
</React.Fragment>
),
},
{
summary: 'What is the validity of the priority support add-on?',
detail: (
<React.Fragment>
The priority support add-on is valid for 1 year from the date of purchase. It is same for
perpetual or annual license model.{' '}
<Link
target="_blank"
rel="noopener"
href="https://mui.com/legal/technical-support-sla/#support-plans"
>
Support plans.
</Link>
</React.Fragment>
),
},
];
const Accordion = styled(MuiAccordion)(({ theme }) => ({
padding: theme.spacing(2),
transition: theme.transitions.create('box-shadow'),
'&&': {
borderRadius: theme.shape.borderRadius,
},
'&:hover': {
borderColor: theme.palette.primary[300],
boxShadow: `0px 4px 8px ${alpha(theme.palette.grey[200], 0.6)}`,
},
'&:not(:last-of-type)': {
marginBottom: theme.spacing(2),
},
'&::before': {
display: 'none',
},
'&::after': {
display: 'none',
},
...theme.applyDarkStyles({
'&:hover': {
borderColor: alpha(theme.palette.primary[600], 0.6),
boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.8)',
},
}),
}));
const AccordionSummary = styled(MuiAccordionSummary)(({ theme }) => ({
padding: theme.spacing(2),
margin: theme.spacing(-2),
minHeight: 'auto',
'&.Mui-expanded': {
minHeight: 'auto',
},
'& .MuiAccordionSummary-content': {
margin: 0,
paddingRight: theme.spacing(2),
'&.Mui-expanded': {
margin: 0,
},
},
}));
const AccordionDetails = styled(MuiAccordionDetail)(({ theme }) => ({
marginTop: theme.spacing(1),
padding: 0,
}));
export default function PricingFAQ() {
function renderItem(index: number) {
const faq = faqData[index];
return (
<Accordion variant="outlined">
<AccordionSummary
expandIcon={<KeyboardArrowDownRounded sx={{ fontSize: 20, color: 'primary.main' }} />}
>
<Typography variant="body2" component="h3" sx={{ fontWeight: 'bold' }}>
{faq.summary}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography
component="div"
variant="body2"
sx={{ color: 'text.secondary', '& ul': { pl: 2 } }}
>
{faq.detail}
</Typography>
</AccordionDetails>
</Accordion>
);
}
return (
<Section cozy>
<Typography id="faq" variant="h2" sx={{ mb: { xs: 2, sm: 4 } }}>
Frequently asked questions
</Typography>
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 4 }}>
{renderItem(0)}
{renderItem(1)}
{renderItem(2)}
{renderItem(3)}
{renderItem(4)}
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
{renderItem(5)}
{renderItem(6)}
{renderItem(7)}
{renderItem(8)}
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Paper
variant="outlined"
sx={(theme) => ({
p: 2,
textAlign: 'center',
borderStyle: 'dashed',
borderColor: 'grey.300',
bgcolor: 'white',
...theme.applyDarkStyles({
borderColor: 'divider',
bgcolor: 'primaryDark.800',
}),
})}
>
<Box sx={{ textAlign: 'left' }}>
<Typography
variant="body2"
component="h3"
sx={{ color: 'text.primary', fontWeight: 'bold' }}
>
Got any questions unanswered or need help?
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary', my: 1, textAlign: 'left' }}>
Email us at <Link href="mailto:sales@mui.com">sales@mui.com</Link> for sales-related
questions.
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', my: 1, textAlign: 'left' }}>
For product-related problems, please open
<Link href="https://github.com/mui/mui-x/issues/new/choose">a new GitHub issue</Link>.
(If you need to share private information, you can{' '}
<Link href="mailto:x@mui.com">email</Link> us.)
</Typography>
</Paper>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,136 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Fade from '@mui/material/Fade';
import Paper, { PaperProps } from '@mui/material/Paper';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import PricingTable from 'docs/src/components/pricing/PricingTable';
import {
PlanPrice,
PlanNameDisplay,
planInfo,
FeatureItem,
} from 'docs/src/components/pricing/PricingCards';
import LicenseModelSwitch from 'docs/src/components/pricing/LicenseModelSwitch';
const Plan = React.forwardRef<
HTMLDivElement,
{
plan: 'community' | 'pro' | 'premium' | 'enterprise';
unavailable?: boolean;
} & PaperProps
>(function Plan({ plan, unavailable, sx, ...props }, ref) {
const { features } = planInfo[plan];
return (
<Paper
ref={ref}
variant="outlined"
sx={{ p: 2, ...(unavailable && { '& .MuiTypography-root': { opacity: 0.5 } }), ...sx }}
{...props}
>
<PlanNameDisplay plan={plan} disableDescription={false} />
<Box sx={{ mb: 2 }}>
{(plan === 'pro' || plan === 'premium') && <LicenseModelSwitch />}
<PlanPrice plan={plan} />
</Box>
{features.map((feature, index) => (
<Box
key={index}
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
mt: 1,
}}
>
<FeatureItem feature={feature} idPrefix={plan} />
</Box>
))}
</Paper>
);
});
export default function PricingList() {
const [planIndex, setPlanIndex] = React.useState(0);
return (
<React.Fragment>
<Tabs
value={planIndex}
variant="fullWidth"
onChange={(event, value) => setPlanIndex(value)}
sx={[
{
mb: 2,
position: 'sticky',
top: 55,
bgcolor: 'background.paper',
zIndex: 1,
mx: { xs: -2, sm: -3 },
borderTop: '1px solid',
borderColor: 'divider',
'& .MuiTab-root': {
borderBottom: '1px solid',
borderColor: 'divider',
'&.Mui-selected': {
bgcolor: 'grey.50',
},
},
},
(theme) =>
theme.applyDarkStyles({
'& .MuiTab-root': {
'&.Mui-selected': {
bgcolor: 'primaryDark.800',
},
},
}),
]}
>
<Tab label="Community" />
<Tab
label="Pro"
sx={{ borderWidth: '0 1px 0 1px', borderStyle: 'solid', borderColor: 'divider' }}
/>
<Tab
label="Premium"
sx={{ borderWidth: '0 1px 0 1px', borderStyle: 'solid', borderColor: 'divider' }}
/>
<Tab label="Enterprise" />
</Tabs>
{planIndex === 0 && (
<Fade in>
<div>
<Plan plan="community" />
<PricingTable columnHeaderHidden plans={['community']} />
</div>
</Fade>
)}
{planIndex === 1 && (
<Fade in>
<div>
<Plan plan="pro" />
<PricingTable columnHeaderHidden plans={['pro']} />
</div>
</Fade>
)}
{planIndex === 2 && (
<Fade in>
<div>
<Plan plan="premium" />
<PricingTable columnHeaderHidden plans={['premium']} />
</div>
</Fade>
)}
{planIndex === 3 && (
<Fade in>
<div>
<Plan plan="enterprise" />
<PricingTable columnHeaderHidden plans={['enterprise']} />
</div>
</Fade>
)}
</React.Fragment>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,206 @@
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import LocalOfferOutlinedIcon from '@mui/icons-material/LocalOfferOutlined';
import FunctionsIcon from '@mui/icons-material/Functions';
import AllInclusiveOutlinedIcon from '@mui/icons-material/AllInclusiveOutlined';
import ReplayRoundedIcon from '@mui/icons-material/ReplayRounded';
import AcUnitIcon from '@mui/icons-material/AcUnit';
import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined';
import PriceChangeIcon from '@mui/icons-material/PriceChange';
import Section from 'docs/src/layouts/Section';
import { Link } from '@mui/docs/Link';
import GradientText from 'docs/src/components/typography/GradientText';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
export default function PricingWhatToExpect() {
return (
<Section cozy>
<SectionHeadline
overline="Paid plans"
title={
<Typography variant="h2" sx={{ mt: 1, mb: 4 }}>
Key information about
<br /> <GradientText>the paid plans</GradientText>
</Typography>
}
/>
<Box
sx={{
columnGap: 3,
columnCount: { sm: 1, md: 2, lg: 3 },
'& > *': {
breakInside: 'avoid',
marginBottom: 2,
},
}}
>
<Paper variant="outlined" sx={{ p: 2, height: 'fit-content', gridColumn: 'span 1' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<FunctionsIcon fontSize="small" color="primary" />
<Typography
component="h3"
variant="body2"
sx={{ fontWeight: 'bold', color: 'text.primary' }}
>
Required quantity
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
The number of developers licensed must correspond to the maximum number of concurrent
developers contributing changes to the front-end code of the projects that use the
software.
<br />
<br />
You can learn more about this in{' '}
<Link
target="_blank"
rel="noopener"
href="https://mui.com/legal/mui-x-eula/#required-quantity-of-licenses"
>
the EULA
</Link>
.
</Typography>
</Paper>
<Paper variant="outlined" sx={{ p: 2, height: 'fit-content' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<AcUnitIcon fontSize="small" color="primary" />
<Typography
component="h3"
variant="body2"
sx={{ fontWeight: 'bold', color: 'text.primary' }}
>
Perpetual license model
</Typography>
</Box>
<Typography variant="body2" component="div" sx={{ color: 'text.secondary' }}>
The Perpetual license model offers the right to keep using your licensed versions
forever in production and development. It comes with 12 months of maintenance (free
updates & support).
<br />
<br />
Upon expiration, you can renew your maintenance plan with a discount that depends on
when you renew:
<ul>
<li>before the support expires: 50% discount</li>
<li>up to 60 days after the support has expired: 35% discount</li>
<li>more than 60 days after the support has expired: 15% discount</li>
</ul>
</Typography>
</Paper>
<Paper variant="outlined" sx={{ p: 2, height: 'fit-content' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<AllInclusiveOutlinedIcon fontSize="small" color="primary" />
<Typography
component="h3"
variant="body2"
sx={{ fontWeight: 'bold', color: 'text.primary' }}
>
Perpetual vs. Annual license model
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
On both license models, any version released before the end of your license term is
forever available for applications deployed in production.
<br />
<br />
The difference regards the right to use the components for <strong>
development
</strong>{' '}
purposes. Only the perpetual license model allows you to continue development once your
license expires.
</Typography>
</Paper>
<Paper variant="outlined" sx={{ p: 2, height: 'fit-content' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<ReplayRoundedIcon fontSize="small" color="primary" />
<Typography
component="h3"
variant="body2"
sx={{ fontWeight: 'bold', color: 'text.primary' }}
>
Annual license model
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
The Annual license model requires an active license to use the software in development.
You will need to renew your license if you wish to continue active development after
your current license term expires.
<br />
<br />
The license is perpetual in production so you {"don't"} need to renew your license if
you have stopped active development with the commercial components.
<br />
<br />
You can learn more about this in{' '}
<Link
target="_blank"
rel="noopener"
href="https://mui.com/legal/mui-x-eula/#annual-license"
>
the EULA
</Link>
.
</Typography>
</Paper>
<Paper variant="outlined" sx={{ p: 2, height: 'fit-content' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<HelpOutlineOutlinedIcon fontSize="small" color="primary" />
<Typography
component="h3"
variant="body2"
sx={{ fontWeight: 'bold', color: 'text.primary' }}
>
Maintenance and support
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
With your purchase, you receive support and access to new versions for the duration of
your subscription. You can{' '}
<Link href="https://mui.com/x/introduction/support/#technical-support">
learn more about support
</Link>{' '}
in the docs.
<br />
<br />
Note that, except for critical issues, such as security flaws, we release bug fixes and
other improvements on top of the latest version, instead of patching older versions.
</Typography>
</Paper>
<Paper variant="outlined" sx={{ p: 2, height: 'fit-content' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<LocalOfferOutlinedIcon fontSize="small" color="primary" />
<Typography
component="h3"
variant="body2"
sx={{ fontWeight: 'bold', color: 'text.primary' }}
>
Volume discounts
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Have a team of 25 or more developers? Get in touch with our{' '}
<Link href="mailto:sales@mui.com">sales team</Link> for a volume discount.
</Typography>
</Paper>
<Paper variant="outlined" sx={{ p: 2, height: 'fit-content' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<PriceChangeIcon fontSize="small" color="primary" />
<Typography
component="h3"
variant="body2"
sx={{ fontWeight: 'bold', color: 'text.primary' }}
>
Price increases
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
To continue providing the best service, MUI may implement an annual price increase of up
to 7% at the time of renewal.
</Typography>
</Paper>
</Box>
</Section>
);
}

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
const PrioritySupport = React.createContext<{
prioritySupport: boolean;
setPrioritySupport: React.Dispatch<React.SetStateAction<boolean>>;
}>(undefined as any);
if (process.env.NODE_ENV !== 'production') {
PrioritySupport.displayName = 'PrioritySupport';
}
export function PrioritySupportProvider(props: any) {
const [prioritySupport, setPrioritySupport] = React.useState<boolean>(false);
const value = React.useMemo(
() => ({ prioritySupport, setPrioritySupport }),
[prioritySupport, setPrioritySupport],
);
return <PrioritySupport.Provider value={value}>{props.children}</PrioritySupport.Provider>;
}
export function usePrioritySupport() {
return React.useContext(PrioritySupport);
}

View File

@@ -0,0 +1,134 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import Switch from '@mui/material/Switch';
import FormGroup from '@mui/material/FormGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import Typography from '@mui/material/Typography';
import { usePrioritySupport } from 'docs/src/components/pricing/PrioritySupportContext';
import Tooltip from '@mui/material/Tooltip';
import Box from '@mui/material/Box';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
export default function PrioritySupportSwitch() {
const { prioritySupport, setPrioritySupport } = usePrioritySupport();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setPrioritySupport(event.target.checked);
};
const prioritySupportDescription =
'At $399/year/dev, get the highest level of support with a 24h SLA response time, pre-screening and issue escalation.';
const tooltipProps = {
enterDelay: 400,
enterNextDelay: 50,
enterTouchDelay: 500,
placement: 'top' as const,
describeChild: true,
slotProps: {
tooltip: {
sx: {
fontSize: 12,
},
},
},
};
return (
<Box
sx={(theme) => ({
border: '1px solid',
borderColor: 'primary.100',
borderRadius: 1,
padding: 2,
...theme.applyDarkStyles({
borderColor: `${alpha(theme.palette.primary[700], 0.4)}`,
}),
})}
>
<FormGroup>
<FormControlLabel
control={<Switch checked={prioritySupport} onChange={handleChange} />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography
fontWeight="semiBold"
color="text.primary"
variant="body2"
sx={{
textAlign: 'left',
whiteSpace: 'nowrap',
}}
>
Priority support
</Typography>
<Tooltip title={prioritySupportDescription} {...tooltipProps}>
<InfoOutlinedIcon sx={{ fontSize: 16, color: 'text.secondary' }} />
</Tooltip>
</Box>
}
sx={{
mb: 0.5,
ml: 0,
mr: 0,
display: 'flex',
justifyContent: 'space-between',
width: '100%',
'& .MuiFormControlLabel-label': {
marginRight: 'auto',
},
}}
labelPlacement="start"
/>
</FormGroup>
<Typography variant="body2" color="text.secondary">
24h SLA response time, support for MUI Core, and the highest priority on bug fixes.
</Typography>
</Box>
);
}
export function PrioritySupportSwitchTable() {
const { prioritySupport, setPrioritySupport } = usePrioritySupport();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setPrioritySupport(event.target.checked);
};
const prioritySupportDescription =
'At $399/year/dev, get the highest level of support with a 24h SLA response time, pre-screening and issue escalation.';
const tooltipProps = {
enterDelay: 400,
enterNextDelay: 50,
enterTouchDelay: 500,
placement: 'top' as const,
describeChild: true,
slotProps: {
tooltip: {
sx: {
fontSize: 12,
},
},
},
};
return (
<FormGroup>
<FormControlLabel
control={<Switch checked={prioritySupport} onChange={handleChange} />}
label={
<Tooltip title={prioritySupportDescription} {...tooltipProps}>
<Typography
variant="body1"
sx={{ color: 'text.secondary', textAlign: 'center', fontSize: '0.875rem' }}
>
Priority support
</Typography>
</Tooltip>
}
sx={{
mb: 0.5,
gap: 1,
}}
labelPlacement="start"
/>
</FormGroup>
);
}

View File

@@ -0,0 +1,101 @@
import SvgIcon from '@mui/material/SvgIcon';
export function ProSupportIcon({ idPrefix = '' }: { idPrefix?: string }) {
const gradientId = `${idPrefix}pro-gradient`;
return (
<SvgIcon inheritViewBox>
<path
d="M10.92 12.75L12 11.93L13.07 12.74C13.46 13.03 13.99 12.66 13.85 12.19L13.43 10.83L14.63 9.88C15 9.6 14.79 9 14.31 9H12.91L12.48 7.66C12.33 7.2 11.68 7.2 11.53 7.66L11.09 9H9.68C9.21 9 9 9.6 9.37 9.89L10.56 10.84L10.14 12.2C10 12.67 10.53 13.04 10.92 12.75ZM6 21.61C6 22.29 6.67 22.77 7.32 22.56L12 21L16.68 22.56C17.33 22.78 18 22.3 18 21.61V15.28C19.24 13.87 20 12.03 20 10C20 5.58 16.42 2 12 2C7.58 2 4 5.58 4 10C4 12.03 4.76 13.87 6 15.28V21.61ZM12 4C15.31 4 18 6.69 18 10C18 13.31 15.31 16 12 16C8.69 16 6 13.31 6 10C6 6.69 8.69 4 12 4Z"
fill="currentColor"
/>
<path
d="M10.92 12.75L12 11.93L13.07 12.74C13.46 13.03 13.99 12.66 13.85 12.19L13.43 10.83L14.63 9.88C15 9.6 14.79 9 14.31 9H12.91L12.48 7.66C12.33 7.2 11.68 7.2 11.53 7.66L11.09 9H9.68C9.21 9 9 9.6 9.37 9.89L10.56 10.84L10.14 12.2C10 12.67 10.53 13.04 10.92 12.75ZM6 21.61C6 22.29 6.67 22.77 7.32 22.56L12 21L16.68 22.56C17.33 22.78 18 22.3 18 21.61V15.28C19.24 13.87 20 12.03 20 10C20 5.58 16.42 2 12 2C7.58 2 4 5.58 4 10C4 12.03 4.76 13.87 6 15.28V21.61ZM12 4C15.31 4 18 6.69 18 10C18 13.31 15.31 16 12 16C8.69 16 6 13.31 6 10C6 6.69 8.69 4 12 4Z"
fill={`url(#${gradientId})`}
fillOpacity="0.8"
/>
<defs>
<linearGradient
id={gradientId}
x1="6.53333"
y1="4.92031"
x2="21.1146"
y2="18.9651"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#B6947E" />
<stop offset="0.2" stopColor="#8F6959" />
<stop offset="0.475" stopColor="#F8DAC8" />
<stop offset="0.67" stopColor="#AC836E" />
<stop offset="0.83" stopColor="#B6947E" />
<stop offset="1" stopColor="#F8DCCB" />
</linearGradient>
</defs>
</SvgIcon>
);
}
export function PremiumSupportIcon({ idPrefix = '' }: { idPrefix?: string }) {
const gradientId = `${idPrefix}premium-gradient`;
return (
<SvgIcon inheritViewBox>
<path
d="M10.92 13.125L12 12.305L13.07 13.115C13.46 13.405 13.99 13.035 13.85 12.565L13.43 11.205L14.63 10.255C15 9.975 14.79 9.375 14.31 9.375H12.91L12.48 8.035C12.33 7.575 11.68 7.575 11.53 8.035L11.09 9.375H9.68C9.21 9.375 9 9.975 9.37 10.265L10.56 11.215L10.14 12.575C10 13.045 10.53 13.415 10.92 13.125ZM6 21.985C6 22.665 6.67 23.145 7.32 22.935L12 21.375L16.68 22.935C17.33 23.155 18 22.675 18 21.985V15.655C19.24 14.245 20 12.405 20 10.375C20 5.955 16.42 2.375 12 2.375C7.58 2.375 4 5.955 4 10.375C4 12.405 4.76 14.245 6 15.655V21.985ZM12 4.375C15.31 4.375 18 7.065 18 10.375C18 13.685 15.31 16.375 12 16.375C8.69 16.375 6 13.685 6 10.375C6 7.065 8.69 4.375 12 4.375Z"
fill="currentColor"
/>
<path
d="M10.92 13.125L12 12.305L13.07 13.115C13.46 13.405 13.99 13.035 13.85 12.565L13.43 11.205L14.63 10.255C15 9.975 14.79 9.375 14.31 9.375H12.91L12.48 8.035C12.33 7.575 11.68 7.575 11.53 8.035L11.09 9.375H9.68C9.21 9.375 9 9.975 9.37 10.265L10.56 11.215L10.14 12.575C10 13.045 10.53 13.415 10.92 13.125ZM6 21.985C6 22.665 6.67 23.145 7.32 22.935L12 21.375L16.68 22.935C17.33 23.155 18 22.675 18 21.985V15.655C19.24 14.245 20 12.405 20 10.375C20 5.955 16.42 2.375 12 2.375C7.58 2.375 4 5.955 4 10.375C4 12.405 4.76 14.245 6 15.655V21.985ZM12 4.375C15.31 4.375 18 7.065 18 10.375C18 13.685 15.31 16.375 12 16.375C8.69 16.375 6 13.685 6 10.375C6 7.065 8.69 4.375 12 4.375Z"
fill={`url(#${gradientId})`}
fillOpacity="0.8"
/>
<defs>
<linearGradient
id={gradientId}
x1="16.8667"
y1="3.23391"
x2="0.741029"
y2="18.9648"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FDFFFE" />
<stop offset="0.39" stopColor="#7A96AC" />
<stop offset="0.635" stopColor="#D4DEE5" />
<stop offset="0.815" stopColor="#7A96AC" />
<stop offset="1" stopColor="#BCCAD7" />
</linearGradient>
</defs>
</SvgIcon>
);
}
export function PrioritySupportIcon({ idPrefix = '' }: { idPrefix?: string }) {
const gradientId = `${idPrefix}priority-gradient`;
return (
<SvgIcon inheritViewBox>
<path
d="M10.92 13.125L12 12.305L13.07 13.115C13.46 13.405 13.99 13.035 13.85 12.565L13.43 11.205L14.63 10.255C15 9.975 14.79 9.375 14.31 9.375H12.91L12.48 8.035C12.33 7.575 11.68 7.575 11.53 8.035L11.09 9.375H9.68C9.21 9.375 9 9.975 9.37 10.265L10.56 11.215L10.14 12.575C10 13.045 10.53 13.415 10.92 13.125ZM6 21.985C6 22.665 6.67 23.145 7.32 22.935L12 21.375L16.68 22.935C17.33 23.155 18 22.675 18 21.985V15.655C19.24 14.245 20 12.405 20 10.375C20 5.955 16.42 2.375 12 2.375C7.58 2.375 4 5.955 4 10.375C4 12.405 4.76 14.245 6 15.655V21.985ZM12 4.375C15.31 4.375 18 7.065 18 10.375C18 13.685 15.31 16.375 12 16.375C8.69 16.375 6 13.685 6 10.375C6 7.065 8.69 4.375 12 4.375Z"
fill="currentColor"
/>
<path
d="M10.92 13.125L12 12.305L13.07 13.115C13.46 13.405 13.99 13.035 13.85 12.565L13.43 11.205L14.63 10.255C15 9.975 14.79 9.375 14.31 9.375H12.91L12.48 8.035C12.33 7.575 11.68 7.575 11.53 8.035L11.09 9.375H9.68C9.21 9.375 9 9.975 9.37 10.265L10.56 11.215L10.14 12.575C10 13.045 10.53 13.415 10.92 13.125ZM6 21.985C6 22.665 6.67 23.145 7.32 22.935L12 21.375L16.68 22.935C17.33 23.155 18 22.675 18 21.985V15.655C19.24 14.245 20 12.405 20 10.375C20 5.955 16.42 2.375 12 2.375C7.58 2.375 4 5.955 4 10.375C4 12.405 4.76 14.245 6 15.655V21.985ZM12 4.375C15.31 4.375 18 7.065 18 10.375C18 13.685 15.31 16.375 12 16.375C8.69 16.375 6 13.685 6 10.375C6 7.065 8.69 4.375 12 4.375Z"
fill={`url(#${gradientId})`}
fillOpacity="0.8"
/>
<defs>
<linearGradient
id={gradientId}
x1="6.13333"
y1="5.98244"
x2="20.2363"
y2="17.0584"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#8C421D" />
<stop offset="0.325272" stopColor="#FBE67B" />
<stop offset="0.535488" stopColor="#FCFBE7" />
<stop offset="0.769917" stopColor="#F7D14E" />
<stop offset="1" stopColor="#D4A041" />
</linearGradient>
</defs>
</SvgIcon>
);
}

View File

@@ -0,0 +1,29 @@
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import IconImage from 'docs/src/components/icon/IconImage';
export default function CoreHero() {
return (
<Section cozy noPaddingBottom>
<SectionHeadline
alwaysCenter
overline={
<Stack direction="row" sx={{ justifyContent: 'center', alignItems: 'center' }}>
<IconImage loading="eager" width={28} height={28} name="product-core" sx={{ mr: 1 }} />{' '}
MUI Core
</Stack>
}
title={
<Typography component="h1" variant="h2" sx={{ textAlign: 'center' }} gutterBottom>
Ready to use components <GradientText>free forever</GradientText>
</Typography>
}
description="Get a growing list of React components and utilities, ready-to-use, free forever, and with
accessibility always in mind. We've built the foundational UI blocks for your design system so you don't have to."
/>
</Section>
);
}

View File

@@ -0,0 +1,65 @@
import Grid from '@mui/material/Grid';
import Section from 'docs/src/layouts/Section';
import { InfoCard } from '@mui/docs/InfoCard';
import { Theme } from '@mui/material/styles';
import SvgMuiLogomark from 'docs/src/icons/SvgMuiLogomark';
import StyleRoundedIcon from '@mui/icons-material/StyleRounded';
import WebRoundedIcon from '@mui/icons-material/WebRounded';
const iconStyles = (theme: Theme) => ({
fontSize: '.875rem',
color: (theme.vars || theme).palette.primary.main,
});
const logoColor = (theme: Theme) => ({
'& path': {
...theme.applyDarkStyles({
fill: (theme.vars || theme).palette.primary[400],
}),
},
});
const content = [
{
icon: <SvgMuiLogomark width={14} height={14} sx={logoColor} />,
title: 'Material UI',
description: "An open-source React component library that implements Google's Material Design.",
link: '/material-ui/',
},
{
icon: <WebRoundedIcon sx={iconStyles} />,
title: 'Joy UI',
description:
"An open-source React component library that implements MUI's own in-house design principles.",
link: '/joy-ui/getting-started/',
},
{
icon: <StyleRoundedIcon sx={iconStyles} />,
title: 'MUI System',
description:
'A set of CSS utilities to help you build custom designs more efficiently. It makes it possible to rapidly lay out custom designs.',
link: '/system/getting-started/',
},
];
export default function CoreProducts() {
return (
<Section cozy>
<Grid container spacing={2}>
{content.map(({ icon, title, description, link }) => (
<Grid key={title} size={{ xs: 12, md: 4 }}>
<InfoCard
icon={icon}
link={link}
title={title}
description={description}
titleProps={{
component: 'h2',
}}
/>
</Grid>
))}
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,264 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Fade from '@mui/material/Fade';
import Typography from '@mui/material/Typography';
import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';
import TextFieldsRounded from '@mui/icons-material/TextFieldsRounded';
import WidgetsRounded from '@mui/icons-material/WidgetsRounded';
import ToggleOnRounded from '@mui/icons-material/ToggleOnRounded';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import More from 'docs/src/components/action/More';
import Frame from 'docs/src/components/action/Frame';
import { Link } from '@mui/docs/Link';
const DEMOS = ['Components', 'Branding', 'Iconography'];
const Image = styled('img')(({ theme }) => ({
transition: '0.4s',
display: 'block',
height: 'auto',
borderRadius: 6,
border: '1px solid',
borderColor: theme.palette.divider,
filter: `drop-shadow(-2px 4px 6px ${alpha(theme.palette.grey[500], 0.5)})`,
...theme.applyDarkStyles({
filter: `drop-shadow(-2px 4px 6px ${alpha(theme.palette.common.black, 0.2)})`,
borderColor: theme.palette.primaryDark[600],
}),
}));
interface MaterialFigmaComponentsProps {
fadeIn?: boolean;
}
export function MaterialFigmaComponents({ fadeIn }: MaterialFigmaComponentsProps) {
return (
<Fade in={fadeIn} timeout={500}>
<Box
sx={[
{
width: '100%',
height: '100%',
'& img': {
position: 'absolute',
left: '50%',
width: { xs: 220, sm: 300 },
'&:nth-of-type(1)': {
top: 120,
transform: 'translate(-70%)',
},
'&:nth-of-type(2)': {
top: 80,
transform: 'translate(-50%)',
},
'&:nth-of-type(3)': {
top: 40,
transform: 'translate(-30%)',
},
},
'&:hover': {
'& img': {
filter: 'drop-shadow(-16px 12px 20px rgba(61, 71, 82, 0.2))',
'&:nth-of-type(1)': {
top: 0,
transform: 'scale(0.8) translate(-110%) rotateY(30deg)',
},
'&:nth-of-type(2)': {
top: 40,
transform: 'scale(0.8) translate(-60%) rotateY(30deg)',
},
'&:nth-of-type(3)': {
top: 40,
transform: 'scale(0.8) translate(-10%) rotateY(30deg)',
},
},
},
},
(theme) =>
theme.applyDarkStyles({
'&:hover': {
'& img': {
filter: 'drop-shadow(-16px 12px 20px rgba(0, 0, 0, 0.4))',
},
},
}),
]}
>
<Image
src="/static/branding/design-kits/Button-light.jpeg"
alt="Material UI Button component variations in the Figma Design Kit."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/Button-dark.jpeg)`,
})
}
/>
<Image
src="/static/branding/design-kits/Alert-light.jpeg"
alt="Material UI Alert component variations in the Figma Design Kit."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/Alert-dark.jpeg)`,
})
}
/>
<Image
src="/static/branding/design-kits/Slider-light.jpeg"
alt="Material UI Slider component variations in the Figma Design Kit."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/Slider-dark.jpeg)`,
})
}
/>
</Box>
</Fade>
);
}
export function MaterialDesignKitInfo() {
return (
<Frame.Info data-mui-color-scheme="dark">
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
<Typography variant="body2" sx={{ fontWeight: 'semiBold' }}>
Available in:
</Typography>
<Box sx={{ display: 'flex', gap: 1, '& >img': { width: 20, height: 20 } }}>
<img src="/static/branding/design-kits/figma-logo.svg" alt="Figma logo." loading="lazy" />
<img
src="/static/branding/design-kits/sketch-logo.svg"
alt="Sketch logo."
loading="lazy"
/>
</Box>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
We frequently update them to stay up-to-date with the latest release.
</Typography>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, gap: 1.5 }}>
<Button
component={Link}
variant="contained"
size="small"
noLinkStyle
href="https://mui.com/store/?utm_source=marketing&utm_medium=referral&utm_campaign=design-cta2#design"
endIcon={<ChevronRightRoundedIcon />}
>
Buy it now
</Button>
<Button
component={Link}
variant="outlined"
size="small"
color="secondary"
href="https://www.figma.com/community/file/912837788133317724/material-ui-for-figma-and-mui-x"
startIcon={
<img
src="/static/branding/design-kits/figma-logo.svg"
alt=""
loading="lazy"
style={{ width: 16, height: 16 }}
/>
}
>
Figma Preview
</Button>
</Box>
</Frame.Info>
);
}
export default function DesignKitsDemo() {
const [demo, setDemo] = React.useState(DEMOS[0]);
const icons = {
[DEMOS[0]]: <ToggleOnRounded fontSize="small" />,
[DEMOS[1]]: <TextFieldsRounded fontSize="small" />,
[DEMOS[2]]: <WidgetsRounded fontSize="small" />,
};
return (
<Section bg="gradient" cozy>
<Grid container spacing={2} alignItems="center">
<Grid sx={{ minWidth: 0 }} size={{ md: 6 }}>
<SectionHeadline
overline="Design Kits"
title={
<Typography variant="h2">
Enhance your <GradientText>design workflow</GradientText>
</Typography>
}
description="The Design Kits contain many of the Material UI components with states, variations, colors, typography, and icons."
/>
<Group desktopColumns={2} sx={{ m: -2, p: 2 }}>
{DEMOS.map((name) => (
<Highlighter key={name} selected={name === demo} onClick={() => setDemo(name)}>
<Item
icon={React.cloneElement(icons[name], name === demo ? { color: 'primary' } : {})}
title={name}
/>
</Highlighter>
))}
<More
component={Link}
href="https://mui.com/store/?utm_source=marketing&utm_medium=referral&utm_campaign=design-cta3#design"
noLinkStyle
/>
</Group>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Frame>
<Frame.Demo sx={{ overflow: 'clip', height: { xs: 240, sm: 390 } }}>
<MaterialFigmaComponents fadeIn={demo === 'Components'} />
<Fade in={demo === 'Branding'} timeout={500}>
<Image
src="/static/branding/design-kits/Colors-light.jpeg"
alt="Available colors on the Material UI Kit."
loading="lazy"
width="300"
sx={(theme) => ({
width: { sm: 400 },
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)',
...theme.applyDarkStyles({
content: `url(/static/branding/design-kits/Colors-dark.jpeg)`,
}),
})}
/>
</Fade>
<Fade in={demo === 'Iconography'} timeout={500}>
<Image
src="/static/branding/design-kits/Icons-light.jpeg"
alt="A bunch of icons available with the Material UI Design Kits."
loading="lazy"
width="300"
sx={(theme) => ({
width: { sm: 500 },
position: 'absolute',
left: '50%',
top: 60,
transform: 'translate(-40%)',
...theme.applyDarkStyles({
content: `url(/static/branding/design-kits/Icons-dark.jpeg)`,
}),
})}
/>
</Fade>
</Frame.Demo>
<MaterialDesignKitInfo />
</Frame>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,201 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import MuiAccordion from '@mui/material/Accordion';
import MuiAccordionSummary from '@mui/material/AccordionSummary';
import MuiAccordionDetail from '@mui/material/AccordionDetails';
import KeyboardArrowDownRounded from '@mui/icons-material/KeyboardArrowDownRounded';
import { Link as InternalLink } from '@mui/docs/Link';
import Section from 'docs/src/layouts/Section';
const faqData = [
{
summary: 'What long-term support do you offer?',
detail: (
<React.Fragment>
We think you&apos;ll love the components we&apos;ve built so far, but we&apos;re planning to
release more. We opened it up as soon as we had something useful, so that you can start
getting value from it right away, and we&apos;ll be adding new features and components based
on our own ideas, and on suggestions from early access customers.
</React.Fragment>
),
},
{
summary: 'How many licenses do I need?',
detail: (
<React.Fragment>
The number of licenses purchased must correspond to the maximum number of editors working
concurrently in a 24 hour period. An editor is somebody contributing changes to the designed
screens that use the Design Kits. No licenses are required for viewing the designs.
</React.Fragment>
),
},
{
summary: 'The Design Kit got an update. How do I get it?',
detail: (
<React.Fragment>
We&apos;ll send you an email when a new release is available. You can access the item on the{' '}
<InternalLink href="https://mui.com/store/account/downloads/">download</InternalLink> page
of your store account and find a detailed description of the changes under
the&quot;Changelog&quot; tab on this page.
</React.Fragment>
),
},
{
summary: 'Is the Material UI Sync plugin paid?',
detail: (
<React.Fragment>
No. We&apos;re still in alpha mode and rolling out more features progressively, as per your
feedback. We might introduce paid tiers in the future, though.
</React.Fragment>
),
},
{
summary: 'Do you offer discounts to educational or non-profit organizations?',
detail: (
<React.Fragment>
<strong>Yes.</strong> We offer a 50% discount on all products licensed to students,
instructors, non-profit, and charity entities. This special discount cannot be combined with
any other type of discount. To qualify for the discount, you need to send us a document
clearly indicating that you are a member of the respective institution. An email from your
official account which bears your signature is sufficient in most cases. For more
information on how to qualify for a discount, please contact sales.
</React.Fragment>
),
},
{
summary: 'Figma or Sketch?',
detail: (
<React.Fragment>
We aim to keep feature parity between the Figma and Sketch kits where possible. We have a
50% off coupon for past customers who want to switch between them.
</React.Fragment>
),
},
];
const Accordion = styled(MuiAccordion)(({ theme }) => ({
padding: theme.spacing(2),
transition: theme.transitions.create('box-shadow'),
'&&': {
borderRadius: theme.shape.borderRadius,
},
'&:hover': {
boxShadow: '1px 1px 8px 0 rgb(90 105 120 / 20%)',
},
'&:not(:last-of-type)': {
marginBottom: theme.spacing(2),
},
'&::before': {
display: 'none',
},
'&::after': {
display: 'none',
},
}));
const AccordionSummary = styled(MuiAccordionSummary)(({ theme }) => ({
padding: theme.spacing(2),
margin: theme.spacing(-2),
minHeight: 'auto',
'&.Mui-expanded': {
minHeight: 'auto',
},
'& .MuiAccordionSummary-content': {
margin: 0,
paddingRight: theme.spacing(2),
'&.Mui-expanded': {
margin: 0,
},
},
}));
const AccordionDetails = styled(MuiAccordionDetail)(({ theme }) => ({
marginTop: theme.spacing(1),
padding: 0,
}));
export default function DesignKitFAQ() {
function renderItem(index: number) {
const faq = faqData[index];
return (
<Accordion variant="outlined">
<AccordionSummary
expandIcon={<KeyboardArrowDownRounded sx={{ fontSize: 20, color: 'primary.main' }} />}
>
<Typography variant="body2" component="h3" sx={{ fontWeight: 'bold' }}>
{faq.summary}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography
component="div"
variant="body2"
sx={{ color: 'text.secondary', '& ul': { pl: 2 } }}
>
{faq.detail}
</Typography>
</AccordionDetails>
</Accordion>
);
}
return (
<Section>
<Typography id="faq" variant="h2" sx={{ mb: { xs: 2, sm: 4 } }}>
Frequently asked questions
</Typography>
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 6 }}>
{renderItem(0)}
{renderItem(1)}
{renderItem(2)}
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
{renderItem(3)}
{renderItem(4)}
<Paper
variant="outlined"
sx={(theme) => ({
p: 2,
pb: 1,
borderStyle: 'dashed',
borderColor: 'grey.300',
bgcolor: 'white',
textAlign: 'left',
...theme.applyDarkStyles({
borderColor: 'primaryDark.600',
bgcolor: 'primaryDark.800',
}),
})}
>
<Typography
variant="body2"
gutterBottom
sx={{ color: 'text.primary', fontWeight: 'bold' }}
>
Still have questions?
</Typography>
<Typography variant="body2" gutterBottom sx={{ color: 'text.primary' }}>
From community help to premium business support, we&apos;re here for you.
</Typography>
<Button
component="a"
// @ts-expect-error
variant="link"
size="small"
href="mailto:sales@mui.com"
endIcon={<KeyboardArrowRightRounded />}
sx={{ ml: -1 }}
>
Contact sales
</Button>
</Paper>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,114 @@
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import GradientText from 'docs/src/components/typography/GradientText';
import HeroContainer from 'docs/src/layouts/HeroContainer';
import IconImage from 'docs/src/components/icon/IconImage';
import GetStartedButtons from 'docs/src/components/home/GetStartedButtons';
import {
DesignKitImagesSet1,
DesignKitImagesSet2,
DesignKitTools,
} from 'docs/src/components/home/DesignKits';
export default function TemplateHero() {
return (
<HeroContainer
linearGradient
left={
<Box sx={{ textAlign: { xs: 'center', md: 'left' } }}>
<Typography
variant="body2"
sx={[
{
fontWeight: 'bold',
},
(theme) => ({
color: 'primary.600',
display: 'flex',
alignItems: 'center',
justifyContent: { xs: 'center', md: 'start' },
'& > *': { mr: 1 },
...theme.applyDarkStyles({
color: 'primary.400',
}),
}),
]}
>
<IconImage width={28} height={28} loading="eager" name="product-designkits" /> Design
Kits
</Typography>
<Typography variant="h1" sx={{ my: 2, maxWidth: 500 }}>
Material UI
<br /> in your favorite
<br /> <GradientText>design tool</GradientText>
</Typography>
<Typography sx={{ color: 'text.secondary', mb: 3, maxWidth: 450 }}>
Pick your favorite design tool to enjoy and use Material UI components. Boost
consistency and facilitate communication when working with developers.
</Typography>
<GetStartedButtons
primaryLabel="Buy now"
primaryUrl="https://mui.com/store/?utm_source=marketing&utm_medium=referral&utm_campaign=design-cta#design"
secondaryLabel="Figma Preview"
secondaryUrl="https://www.figma.com/community/file/912837788133317724/material-ui-for-figma-and-mui-x"
/>
</Box>
}
right={
<Box sx={{ position: 'relative', height: '100%', perspective: '1000px' }}>
<DesignKitTools />
<Box
sx={(theme) => ({
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 1,
background: `linear-gradient(90deg, ${alpha(
theme.palette.primaryDark[900],
0.8,
)} 1%, ${alpha(theme.palette.primaryDark[900], 0.1)})`,
opacity: 0,
...theme.applyDarkStyles({
opacity: 1,
}),
})}
/>
<Box
sx={{
left: '40%',
position: 'absolute',
display: 'flex',
transform: 'translateX(-40%) rotateZ(30deg) rotateX(8deg) rotateY(-8deg)',
transformOrigin: 'center center',
}}
>
<DesignKitImagesSet1
keyframes={{
'0%': {
transform: 'translateY(-200px)',
},
'100%': {
transform: 'translateY(0px)',
},
}}
/>
<DesignKitImagesSet2
keyframes={{
'0%': {
transform: 'translateY(150px)',
},
'100%': {
transform: 'translateY(-80px)',
},
}}
sx={{ ml: { xs: 2, sm: 4, md: 8 } }}
/>
</Box>
</Box>
}
/>
);
}

View File

@@ -0,0 +1,53 @@
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import Palette from '@mui/icons-material/Palette';
import LibraryBooks from '@mui/icons-material/LibraryBooks';
import { InfoCard } from '@mui/docs/InfoCard';
import CodeRounded from '@mui/icons-material/CodeRounded';
import GradientText from 'docs/src/components/typography/GradientText';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
const content = [
{
icon: <Palette fontSize="small" color="primary" />,
title: 'For designers',
description:
'Save time getting the Material UI components all setup, leveraging the latest features from your favorite design tool.',
},
{
icon: <LibraryBooks fontSize="small" color="primary" />,
title: 'For product managers',
description:
'Quickly put together ideas and high-fidelity mockups/prototypes using components from your actual product.',
},
{
icon: <CodeRounded fontSize="small" color="primary" />,
title: 'For developers',
description:
'Effortlessly communicate with designers using the same language around the Material UI components props and variants.',
},
];
export default function DesignKitValues() {
return (
<Section cozy>
<SectionHeadline
overline="Collaboration"
title={
<Typography variant="h2" sx={{ mt: 1 }}>
Be more efficient <GradientText>designing and developing</GradientText> with the same
library
</Typography>
}
/>
<Grid container spacing={3} mt={4}>
{content.map(({ icon, title, description }) => (
<Grid key={title} size={{ xs: 12, sm: 6, md: 4 }}>
<InfoCard title={title} icon={icon} description={description} />
</Grid>
))}
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,391 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Fade from '@mui/material/Fade';
import FormatShapesRoundedIcon from '@mui/icons-material/FormatShapesRounded';
import SvgStorybook from 'docs/src/icons/SvgStorybook';
import ImagesearchRollerRoundedIcon from '@mui/icons-material/ImagesearchRollerRounded';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import Frame from 'docs/src/components/action/Frame';
import { Link } from '@mui/docs/Link';
const Image = styled('img')(({ theme }) => ({
transition: '0.4s',
display: 'block',
height: 'auto',
borderRadius: 6,
border: '1px solid',
borderColor: theme.palette.divider,
filter: `drop-shadow(-2px 4px 6px ${alpha(theme.palette.grey[500], 0.5)})`,
...theme.applyDarkStyles({
filter: `drop-shadow(-2px 4px 6px ${alpha(theme.palette.common.black, 0.2)})`,
borderColor: theme.palette.primaryDark[600],
}),
}));
export default function ConnectFeatures() {
const [index, setIndex] = React.useState(0);
function getSelectedProps(i: number) {
return {
selected: index === i,
sx: { '& svg': { opacity: index === i ? 1 : 0.5 } },
};
}
return (
<Section>
<Grid container spacing={2}>
<Grid sx={{ minWidth: 0 }} size={{ md: 6 }}>
<SectionHeadline
overline="Available in Beta"
title={
<Typography variant="h2">
The way developers and designers <GradientText>ship faster</GradientText>
</Typography>
}
description="The Sync plugin is perfect for designing and developing using the Material UI React library and Design Kit."
/>
<Group sx={{ m: -2, p: 2 }}>
<Highlighter disableBorder {...getSelectedProps(0)} onClick={() => setIndex(0)}>
<Item
icon={<ImagesearchRollerRoundedIcon color="primary" />}
title="Theme customization"
description="Generate theme code with custom colors, typography styles, shadows, spacing values, and border-radius."
/>
</Highlighter>
<Highlighter disableBorder {...getSelectedProps(1)} onClick={() => setIndex(1)}>
<Item
icon={<FormatShapesRoundedIcon color="primary" />}
title="Component customization"
description="Fully customize a component's design across multiple states and then generate the corresponding theme code."
/>
</Highlighter>
<Highlighter disableBorder {...getSelectedProps(2)} onClick={() => setIndex(2)}>
<Item
icon={<SvgStorybook />}
title="Preview your changes on Storybook"
description="Quickly visualize all the changes you run through Sync on a built-in Storybook preview instance."
/>
</Highlighter>
</Group>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Frame sx={{ height: '100%' }}>
<Frame.Demo
sx={{ overflow: 'clip', height: { xs: 240, sm: 420 }, perspective: '1000px' }}
>
<Box
sx={{
width: '100%',
height: '100%',
'& img': {
position: 'absolute',
top: '10%',
left: '10%',
},
}}
>
{index === 0 && (
<Fade in={index === 0} timeout={500}>
<Box
sx={(theme) => ({
width: '100%',
height: '100%',
'& img': {
position: 'absolute',
'&:nth-of-type(1)': {
visibility: { xs: 'hidden', sm: 'visible' },
width: { xs: 240, sm: 600 },
top: 100,
left: '50%',
transform: 'translate(-40%)',
},
'&:nth-of-type(2)': {
width: { xs: 240, sm: 560 },
top: { xs: 100, sm: 40 },
left: { xs: '60%', sm: '40%' },
transform: {
xs: 'scale(1.8) translate(-20%)',
sm: 'scale(1) translate(0%)',
},
},
},
'&:hover': {
'& img': {
'&:nth-of-type(2)': {
top: { xs: 100, sm: 60 },
transform: {
xs: 'scale(1.8) translate(-20%)',
sm: 'scale(1.1) translate(-15%)',
},
filter: {
xs: 'auto',
sm: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.grey[600],
0.5,
)})`,
},
},
},
},
...theme.applyDarkStyles({
'&:hover': {
'& img': {
'&:nth-of-type(2)': {
filter: {
xs: 'auto',
sm: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.common.black,
0.8,
)})`,
},
},
filter: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.common.black,
0.2,
)})`,
},
},
}),
})}
>
<Image
src="/static/branding/design-kits/sync-base1-light.png"
alt="The Material UI Design Kit for Figma."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/sync-base1-dark.png)`,
})
}
/>
<Image
src="/static/branding/design-kits/sync-shot1-light.png"
alt="The Material UI Sync plugin displaying theme code."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/sync-shot1-dark.png)`,
})
}
/>
</Box>
</Fade>
)}
{index === 1 && (
<Fade in={index === 1} timeout={500}>
<Box
sx={(theme) => ({
width: '100%',
height: '100%',
'& img': {
position: 'absolute',
'&:nth-of-type(1)': {
visibility: { xs: 'hidden', sm: 'visible' },
width: { xs: 240, sm: 600 },
top: 100,
left: '50%',
transform: 'translate(-40%)',
},
'&:nth-of-type(2)': {
width: { xs: 240, sm: 560 },
top: { xs: 100, sm: 40 },
left: { xs: '60%', sm: '50%' },
transform: {
xs: 'scale(1.8) translate(-20%)',
sm: 'none',
},
},
},
'&:hover': {
'& img': {
'&:nth-of-type(2)': {
top: { xs: 100, sm: 60 },
transform: {
xs: 'scale(1.8) translate(-20%)',
sm: 'scale(1.1) translate(-30%)',
},
filter: {
xs: 'auto',
sm: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.grey[600],
0.5,
)})`,
},
},
},
},
...theme.applyDarkStyles({
'&:hover': {
'& img': {
'&:nth-of-type(2)': {
filter: {
xs: 'auto',
sm: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.common.black,
0.8,
)})`,
},
},
filter: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.common.black,
0.2,
)})`,
},
},
}),
})}
>
<Image
src="/static/branding/design-kits/sync-base2-light.png"
alt="The Material UI Design Kit for Figma."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/sync-base2-dark.png)`,
})
}
/>
<Image
src="/static/branding/design-kits/material-sync-light.png"
alt="The Material UI Sync plugin displaying theme code."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/material-sync-dark.png)`,
})
}
/>
</Box>
</Fade>
)}
{index === 2 && (
<Fade in={index === 2} timeout={500}>
<Box
sx={(theme) => ({
width: '100%',
height: '100%',
'& img': {
position: 'absolute',
'&:nth-of-type(1)': {
visibility: { xs: 'hidden', sm: 'visible' },
width: { xs: 240, sm: 600 },
top: 100,
left: '50%',
transform: 'translate(-40%)',
},
'&:nth-of-type(2)': {
width: { xs: 240, sm: 560 },
top: { xs: 100, sm: 40 },
left: { xs: '60%', sm: '40%' },
transform: {
xs: 'scale(1.8) translate(-20%)',
sm: 'none',
},
},
},
'&:hover': {
'& img': {
'&:nth-of-type(2)': {
top: { xs: 100, sm: 60 },
transform: {
xs: 'scale(1.8) translate(-20%)',
sm: 'scale(1.1) translate(-25%)',
},
filter: {
xs: 'auto',
sm: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.grey[600],
0.5,
)})`,
},
},
},
},
...theme.applyDarkStyles({
'&:hover': {
'& img': {
'&:nth-of-type(2)': {
filter: {
xs: 'auto',
sm: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.common.black,
0.8,
)})`,
},
},
filter: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.common.black,
0.2,
)})`,
},
},
}),
})}
>
<Image
src="/static/branding/design-kits/sync-base2-light.png"
alt="The Material UI Design Kit for Figma."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/sync-base2-dark.png)`,
})
}
/>
<Image
src="/static/branding/design-kits/sync-shot3-light.png"
alt="The Material UI Sync plugin displaying theme code."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/sync-shot3-dark.png)`,
})
}
/>
</Box>
</Fade>
)}
</Box>
</Frame.Demo>
<Frame.Info data-mui-color-scheme="dark">
<Typography variant="body2" gutterBottom sx={{ fontWeight: 'bold' }}>
Get the beta version of Material UI Sync now!
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
There&apos;s still a lot to do, and we&apos;re looking forward to hearing from all
of you.
</Typography>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, gap: 1.5 }}>
<Button
component={Link}
variant="contained"
size="small"
noLinkStyle
href="https://www.figma.com/community/plugin/1336346114713490235/material-ui-sync"
>
Use Sync now
</Button>
<Button
component={Link}
variant="outlined"
color="secondary"
size="small"
href="/material-ui/design-resources/material-ui-sync/"
>
View documentation
</Button>
</Box>
</Frame.Info>
</Frame>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,298 @@
import * as React from 'react';
import { CssVarsProvider } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Paper from '@mui/material/Paper';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Tooltip from '@mui/material/Tooltip';
import InputRounded from '@mui/icons-material/InputRounded';
import SmartButtonRounded from '@mui/icons-material/SmartButtonRounded';
import TableViewRounded from '@mui/icons-material/TableViewRounded';
import WarningRounded from '@mui/icons-material/WarningRounded';
import ShoppingCartRounded from '@mui/icons-material/ShoppingCartRounded';
import InfoRounded from '@mui/icons-material/InfoRounded';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import More from 'docs/src/components/action/More';
import Frame from 'docs/src/components/action/Frame';
import { ShowcaseCodeWrapper } from 'docs/src/components/home/ShowcaseContainer';
import { customTheme } from 'docs/src/components/home/MaterialDesignComponents';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import MaterialVsCustomToggle from 'docs/src/components/action/MaterialVsCustomToggle';
import ROUTES from 'docs/src/route';
const DEMOS = ['Button', 'Text Field', 'Table', 'Alert', 'Tooltip'] as const;
const CODES = {
Button: `
<Button variant="text" startIcon={<ShoppingCartRounded />}>
Add item
</Button>
<Button variant="contained" startIcon={<ShoppingCartRounded />}>
Add item
</Button>
<Button variant="outlined" startIcon={<ShoppingCartRounded />}>
Add item
</Button>
`,
'Text Field': `
<TextField variant="standard" label="Username" />
<TextField variant="outlined" label="Email" type="email" />
<TextField variant="filled" label="Password" type="password" />
`,
Table: `
<TableContainer
component={Paper}
variant="outlined"
>
<Table aria-label="demo table">
<TableHead>
<TableRow>
<TableCell>Dessert</TableCell>
<TableCell>Calories</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Frozen yoghurt</TableCell>
<TableCell>109</TableCell>
</TableRow>
<TableRow>
<TableCell>Cupcake</TableCell>
<TableCell>305</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
`,
Alert: `
<Alert variant="standard" color="info">
This is an alert!
</Alert>
<Alert variant="outlined" color="info">
This is an alert!
</Alert>
<Alert variant="filled" color="info">
This is an alert!
</Alert>
`,
Tooltip: `
<Tooltip title="This is a tooltip" arrow placement="top">
<Typography>Top</Typography>
</Tooltip>
<Tooltip title="This is a tooltip" arrow placement="right">
<Typography>Right</Typography>
</Tooltip>
<Tooltip title="This is a tooltip" arrow placement="left">
<Typography>Left</Typography>
</Tooltip>
<Tooltip title="This is a tooltip" arrow placement="bottom">
<Typography>Bottom</Typography>
</Tooltip>
`,
};
export default function MaterialComponents() {
const [demo, setDemo] = React.useState<(typeof DEMOS)[number]>(DEMOS[0]);
const [customized, setCustomized] = React.useState(false);
const icons = {
[DEMOS[0]]: <SmartButtonRounded fontSize="small" />,
[DEMOS[1]]: <InputRounded fontSize="small" />,
[DEMOS[2]]: <TableViewRounded fontSize="small" />,
[DEMOS[3]]: <WarningRounded fontSize="small" />,
[DEMOS[4]]: <InfoRounded fontSize="small" />,
};
return (
<Section bg="gradient">
<Grid container spacing={2}>
<Grid sx={{ minWidth: 0 }} size={{ md: 6 }}>
<SectionHeadline
overline="Component library"
title={
<Typography variant="h2">
<GradientText>40+</GradientText> building block components
</Typography>
}
description="A meticulous implementation of Material Design; every Material UI component meets the highest standards of form and function."
/>
<Group desktopColumns={2} sx={{ m: -2, p: 2 }}>
{DEMOS.map((name) => (
<Highlighter key={name} selected={name === demo} onClick={() => setDemo(name)}>
<Item icon={React.cloneElement(icons[name])} title={name} />
</Highlighter>
))}
<More href={ROUTES.components} />
</Group>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Frame sx={{ height: '100%' }}>
<Frame.Demo className="mui-default-theme" sx={{ flexGrow: 1 }}>
<CssVarsProvider theme={customized ? customTheme : undefined}>
{demo === 'Button' && (
<Box
sx={{
height: '100%',
py: 5,
gap: 2,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap',
}}
>
<Button variant="text" startIcon={<ShoppingCartRounded />}>
Add item
</Button>
<Button variant="contained" startIcon={<ShoppingCartRounded />}>
Add item
</Button>
<Button variant="outlined" startIcon={<ShoppingCartRounded />}>
Add item
</Button>
</Box>
)}
{demo === 'Text Field' && (
<Stack
justifyContent="center"
spacing={2}
sx={{ p: 2, width: '50%', margin: 'auto' }}
>
<TextField variant="standard" label="Username" />
<TextField variant="outlined" label="Email" type="email" />
<TextField
variant="filled"
label="Password"
type="password"
autoComplete="new-password" // prevent chrome auto-fill
/>
</Stack>
)}
{demo === 'Table' && (
<TableContainer
component={Paper}
variant="outlined"
sx={{
mx: 'auto',
my: 4,
maxWidth: '90%',
'& .MuiTableBody-root > .MuiTableRow-root:last-of-type > .MuiTableCell-root':
{
borderBottomWidth: 0,
},
}}
>
<Table aria-label="demo table">
<TableHead>
<TableRow>
<TableCell>Dessert</TableCell>
<TableCell>Calories</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Frozen yoghurt</TableCell>
<TableCell>109</TableCell>
</TableRow>
<TableRow>
<TableCell>Cupcake</TableCell>
<TableCell>305</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
)}
{demo === 'Alert' && (
<Box
sx={{
height: '100%',
py: 2,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap',
gap: 2,
}}
>
<Alert variant="standard" color="info">
This is an alert!
</Alert>
<Alert variant="outlined" color="info">
This is an alert!
</Alert>
<Alert variant="filled" color="info">
This is an alert!
</Alert>
</Box>
)}
{demo === 'Tooltip' && (
<Stack
alignItems="center"
justifyContent="center"
spacing={1}
sx={{ minHeight: 100, py: 2 }}
>
<Tooltip
title="Appears on hover"
arrow
placement="top"
slotProps={{ popper: { disablePortal: true } }}
>
<Typography color="text.secondary">Top</Typography>
</Tooltip>
<Box sx={{ '& > *': { display: 'inline-block' } }}>
<Tooltip
title="Always display"
arrow
placement="left"
open
slotProps={{ popper: { disablePortal: true } }}
>
<Typography color="text.secondary">Left</Typography>
</Tooltip>
<Box sx={{ display: 'inline-block', width: 80 }} />
<Tooltip
title="Appears on hover"
arrow
placement="right"
slotProps={{ popper: { disablePortal: true } }}
>
<Typography color="text.secondary">Right</Typography>
</Tooltip>
</Box>
<Tooltip
title="Appears on hover"
arrow
placement="bottom"
slotProps={{ popper: { disablePortal: true } }}
>
<Typography color="text.secondary">Bottom</Typography>
</Tooltip>
</Stack>
)}
</CssVarsProvider>
</Frame.Demo>
<Frame.Info data-mui-color-scheme="dark" sx={{ p: 0 }}>
<MaterialVsCustomToggle customized={customized} setCustomized={setCustomized} />
<ShowcaseCodeWrapper maxHeight={demo === 'Table' ? 220 : 350} hasDesignToggle>
<HighlightedCode copyButtonHidden plainStyle code={CODES[demo]} language="jsx" />
</ShowcaseCodeWrapper>
</Frame.Info>
</Frame>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,202 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Fade from '@mui/material/Fade';
import Typography from '@mui/material/Typography';
import ExtensionRoundedIcon from '@mui/icons-material/ExtensionRounded';
import DrawRoundedIcon from '@mui/icons-material/DrawRounded';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import Frame from 'docs/src/components/action/Frame';
import {
MaterialDesignKitInfo,
MaterialFigmaComponents,
} from 'docs/src/components/productDesignKit/DesignKitDemo';
import { Link } from '@mui/docs/Link';
const Image = styled('img')(({ theme }) => ({
transition: '0.4s',
display: 'block',
height: 'auto',
borderRadius: 6,
border: '1px solid',
borderColor: theme.palette.divider,
filter: `drop-shadow(-2px 4px 6px ${alpha(theme.palette.grey[500], 0.5)})`,
...theme.applyDarkStyles({
filter: `drop-shadow(-2px 4px 6px ${alpha(theme.palette.common.black, 0.2)})`,
borderColor: theme.palette.primaryDark[600],
}),
}));
interface MaterialDesignKitsProps {
gradient?: boolean;
}
export default function MaterialDesignKits({ gradient }: MaterialDesignKitsProps) {
const [customized, setCustomized] = React.useState(true);
return (
<Section cozy bg={gradient ? 'gradient' : 'white'}>
<Grid container spacing={2} alignItems="center">
<Grid sx={{ minWidth: 0 }} size={{ md: 6 }}>
<SectionHeadline
overline="Design resources"
title={
<Typography variant="h2">
Enhance your <GradientText>design workflow</GradientText>
</Typography>
}
description="Reach out for the Figma Design Kit and the Sync plugin to bridge the gap between development and design when using Material UI."
/>
<Group sx={{ m: -2, p: 2 }}>
<Highlighter disableBorder selected={customized} onClick={() => setCustomized(true)}>
<Item
icon={<DrawRoundedIcon color="primary" />}
title="Design Kit"
description="Get many Material UI components with states, variations, colors, typography, and icons on your preferred design tool."
/>
</Highlighter>
<Highlighter disableBorder selected={!customized} onClick={() => setCustomized(false)}>
<Item
icon={<ExtensionRoundedIcon color="primary" />}
title="Sync plugin"
description="Quickly generate a Material UI theme file with token and component customizations done on Figma."
/>
</Highlighter>
</Group>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Frame>
<Frame.Demo sx={{ overflow: 'clip', height: { xs: 240, sm: 420 } }}>
<MaterialFigmaComponents fadeIn={customized} />
<Fade in={!customized} timeout={500}>
<Box
sx={(theme) => ({
display: !customized ? 'auto' : 'none',
width: '100%',
height: '100%',
'& img': {
position: 'absolute',
'&:nth-of-type(1)': {
visibility: { xs: 'hidden', sm: 'visible' },
width: { xs: 240, sm: 600 },
top: 100,
left: '50%',
transform: 'translate(-40%)',
},
'&:nth-of-type(2)': {
width: { xs: 240, sm: 560 },
top: { xs: 100, sm: 40 },
left: { xs: '60%', sm: '60%' },
transform: {
xs: 'scale(1.8) translate(-20%)',
sm: 'none',
},
},
},
'&:hover': {
'& img': {
'&:nth-of-type(2)': {
top: { xs: 100, sm: 60 },
transform: {
xs: 'scale(1.8) translate(-20%)',
sm: 'scale(1.1) translate(-30%)',
},
filter: {
xs: 'auto',
sm: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.grey[600],
0.5,
)})`,
},
},
},
},
...theme.applyDarkStyles({
'&:hover': {
'& img': {
'&:nth-of-type(2)': {
filter: {
xs: 'auto',
sm: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.common.black,
0.8,
)})`,
},
},
filter: `drop-shadow(-16px 12px 20px ${alpha(
theme.palette.common.black,
0.2,
)})`,
},
},
}),
})}
>
<Image
src="/static/branding/design-kits/sync-base2-light.png"
alt="A bunch of customized Material UI buttons in the Figma Design Kit."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/sync-base2-dark.png)`,
})
}
/>
<Image
src="/static/branding/design-kits/material-sync-light.png"
alt="The Material UI Sync plugin running and showing code for customized buttons."
loading="lazy"
sx={(theme) =>
theme.applyDarkStyles({
content: `url(/static/branding/design-kits/material-sync-dark.png)`,
})
}
/>
</Box>
</Fade>
</Frame.Demo>
{customized ? (
<MaterialDesignKitInfo />
) : (
<Frame.Info data-mui-color-scheme="dark">
<Typography variant="body2" gutterBottom sx={{ fontWeight: 'bold' }}>
Get the beta version of Material UI Sync now!
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
There&apos;s still a lot to do, and we&apos;re looking forward to hearing from all
of you.
</Typography>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, gap: 1.5 }}>
<Button
component={Link}
variant="contained"
size="small"
noLinkStyle
href="https://www.figma.com/community/plugin/1336346114713490235/material-ui-sync"
>
Use Sync now
</Button>
<Button
component={Link}
variant="outlined"
color="secondary"
size="small"
href="/material-ui/design-resources/material-ui-sync/"
>
View documentation
</Button>
</Box>
</Frame.Info>
)}
</Frame>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,107 @@
import * as React from 'react';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import Typography from '@mui/material/Typography';
import StyleRoundedIcon from '@mui/icons-material/StyleRounded';
import { GlowingIconContainer } from '@mui/docs/InfoCard';
import GetStartedButtons from 'docs/src/components/home/GetStartedButtons';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import { Link } from '@mui/docs/Link';
import ROUTES from 'docs/src/route';
interface MaterialEndProps {
noFaq?: boolean;
}
export default function MaterialEnd({ noFaq }: MaterialEndProps) {
return (
<Section
cozy
data-mui-color-scheme="dark"
sx={{
color: 'text.secondary',
background: (theme) =>
`linear-gradient(180deg, ${(theme.vars || theme).palette.primaryDark[900]} 50%,
${alpha(theme.palette.primary[800], 0.2)} 100%), ${
(theme.vars || theme).palette.primaryDark[900]
}`,
}}
>
{noFaq ? (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<SectionHeadline
alwaysCenter
overline="Community"
title={
<Typography variant="h2">
Join our <GradientText>global community</GradientText>
</Typography>
}
description={
<React.Fragment>
Material UI wouldn&apos;t be possible without our global community of contributors.
Join us today to get help when you need it, and lend a hand when you can.
</React.Fragment>
}
/>
<GetStartedButtons
primaryUrl={ROUTES.materialDocs}
secondaryLabel="View templates"
secondaryUrl={ROUTES.freeTemplates}
altInstallation="npm install @mui/material @emotion/react @emotion/styled"
/>
</Box>
) : (
<Grid container spacing={{ xs: 6, sm: 10 }} alignItems="center">
<Grid size={{ xs: 12, sm: 6 }}>
<SectionHeadline
overline="Community"
title={
<Typography variant="h2">
Join our <GradientText>global community</GradientText>
</Typography>
}
description={
<React.Fragment>
Material UI wouldn&apos;t be possible without our global community of
contributors. Join us today to get help when you need it, and lend a hand when you
can.
</React.Fragment>
}
/>
<GetStartedButtons
primaryUrl={ROUTES.materialDocs}
secondaryLabel="View templates"
secondaryUrl={ROUTES.freeTemplates}
altInstallation="npm install @mui/material @emotion/react @emotion/styled"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<List sx={{ '& > li': { alignItems: 'flex-start' } }}>
<ListItem sx={{ p: 0, gap: 2.5 }}>
<GlowingIconContainer icon={<StyleRoundedIcon color="primary" />} />
<div>
<Typography gutterBottom sx={{ color: 'text.primary', fontWeight: 'semiBold' }}>
Does it support Material Design 3?
</Typography>
<Typography>
Material UI currently adopts Material Design 2. You can follow{' '}
<Link href="https://github.com/mui/material-ui/issues/29345">
this GitHub issue
</Link>{' '}
for future design-related updates.
</Typography>
</div>
</ListItem>
</List>
</Grid>
</Grid>
)}
</Section>
);
}

View File

@@ -0,0 +1,494 @@
import * as React from 'react';
import { extendTheme, CssVarsProvider } from '@mui/material/styles';
import Alert from '@mui/material/Alert';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Checkbox from '@mui/material/Checkbox';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import CardMedia from '@mui/material/CardMedia';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import ListItemText from '@mui/material/ListItemText';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import TextField from '@mui/material/TextField';
import Slider from '@mui/material/Slider';
import Stack from '@mui/material/Stack';
import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepLabel from '@mui/material/StepLabel';
import CheckCircleRounded from '@mui/icons-material/CheckCircleRounded';
import CakeRounded from '@mui/icons-material/CakeRounded';
import CelebrationRounded from '@mui/icons-material/CelebrationRounded';
import AttractionsRounded from '@mui/icons-material/AttractionsRounded';
import NotificationsIcon from '@mui/icons-material/Notifications';
import DownloadIcon from '@mui/icons-material/Download';
import LocalFireDepartment from '@mui/icons-material/LocalFireDepartment';
import AcUnitRounded from '@mui/icons-material/AcUnitRounded';
import FavoriteBorderRounded from '@mui/icons-material/FavoriteBorderRounded';
import ShareRounded from '@mui/icons-material/ShareRounded';
import RateReviewOutlined from '@mui/icons-material/RateReviewOutlined';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMore';
import Rating from '@mui/material/Rating';
import Switch from '@mui/material/Switch';
import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft';
import FormatAlignCenterIcon from '@mui/icons-material/FormatAlignCenter';
import FormatAlignRightIcon from '@mui/icons-material/FormatAlignRight';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Badge from '@mui/material/Badge';
import AddIcon from '@mui/icons-material/Add';
import RemoveIcon from '@mui/icons-material/Remove';
import ButtonGroup from '@mui/material/ButtonGroup';
import IconImage from 'docs/src/components/icon/IconImage';
import HeroContainer from 'docs/src/layouts/HeroContainer';
import GetStartedButtons from 'docs/src/components/home/GetStartedButtons';
import GradientText from 'docs/src/components/typography/GradientText';
import { getDesignTokens } from '@mui/docs/branding';
import { Link } from '@mui/docs/Link';
import ROUTES from 'docs/src/route';
function Checkboxes() {
const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
return (
<React.Fragment>
<Checkbox {...label} defaultChecked />
<Checkbox {...label} />
</React.Fragment>
);
}
function ToggleButtons() {
const [alignment, setAlignment] = React.useState('left');
return (
<Paper elevation={0} variant="outlined" sx={{ p: 2 }}>
<ToggleButtonGroup
value={alignment}
exclusive
onChange={(event, newAlignment) => {
setAlignment(newAlignment);
}}
aria-label="text alignment"
>
<ToggleButton value="left" aria-label="left aligned" size="small">
<FormatAlignLeftIcon fontSize="small" />
</ToggleButton>
<ToggleButton value="center" aria-label="centered" size="small">
<FormatAlignCenterIcon fontSize="small" />
</ToggleButton>
<ToggleButton value="right" aria-label="right aligned" size="small" disabled>
<FormatAlignRightIcon fontSize="small" />
</ToggleButton>
</ToggleButtonGroup>
</Paper>
);
}
function TabsDemo() {
const [index, setIndex] = React.useState(0);
return (
<Paper>
<Tabs
value={index}
onChange={(event, newIndex) => setIndex(newIndex)}
variant="fullWidth"
aria-label="icon label tabs example"
>
<Tab icon={<CakeRounded fontSize="small" />} label="Cakes" />
<Tab icon={<CelebrationRounded fontSize="small" />} label="Party" />
<Tab icon={<AttractionsRounded fontSize="small" />} label="Park" />
</Tabs>
</Paper>
);
}
function BadgeVisibilityDemo() {
const [count, setCount] = React.useState(1);
return (
<Paper
variant="outlined"
elevation={0}
sx={{
width: '100%',
color: 'action.active',
p: 2,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
'& .MuiBadge-root': {
marginRight: 4,
},
}}
>
<div>
<Badge color="primary" badgeContent={count}>
<NotificationsIcon fontSize="small" />
</Badge>
<ButtonGroup>
<Button
size="small"
aria-label="reduce"
onClick={() => {
setCount(Math.max(count - 1, 0));
}}
>
<RemoveIcon fontSize="small" />
</Button>
<Button
size="small"
aria-label="increase"
onClick={() => {
setCount(count + 1);
}}
>
<AddIcon fontSize="small" />
</Button>
</ButtonGroup>
</div>
</Paper>
);
}
function SwitchToggleDemo() {
const label = { inputProps: { 'aria-label': 'Switch demo' } };
return (
<Box
sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
>
<Switch {...label} defaultChecked />
<Switch {...label} />
<Checkboxes />
<ToggleButtons />
</Box>
);
}
function SlideDemo() {
const [value, setValue] = React.useState([30, 60]);
return (
<Stack spacing={2} direction="row" sx={{ alignItems: 'center' }}>
<AcUnitRounded
fontSize="small"
color="primary"
sx={{ opacity: `max(0.4, ${(100 - value[0]) / 100})` }}
/>
<Slider
aria-labelledby="temperature-slider"
value={value}
onChange={(_, newValue) => setValue(newValue)}
/>
<LocalFireDepartment
fontSize="small"
color="error"
sx={{ opacity: `max(0.4, ${value[1] / 100})` }}
/>
</Stack>
);
}
const { palette: lightPalette } = getDesignTokens('light');
const { palette: darkPalette } = getDesignTokens('dark');
const customTheme = extendTheme({
cssVarPrefix: 'hero',
colorSchemeSelector: 'data-mui-color-scheme',
colorSchemes: {
light: {
palette: {
...(lightPalette?.primary && { primary: lightPalette?.primary }),
...(lightPalette?.grey && { grey: lightPalette?.grey }),
...(lightPalette?.background && { background: lightPalette?.background }),
},
},
dark: {
palette: {
...(darkPalette?.primary && { primary: darkPalette?.primary }),
...(darkPalette?.grey && { grey: darkPalette?.grey }),
...(darkPalette?.background && { background: darkPalette?.background }),
},
},
},
});
export default function MaterialHero() {
return (
<HeroContainer
linearGradient
left={
<Box sx={{ textAlign: { xs: 'center', md: 'left' } }}>
<Typography
variant="body2"
sx={[
{
fontWeight: 'bold',
},
(theme) => ({
color: 'primary.600',
display: 'flex',
alignItems: 'center',
gap: 1,
justifyContent: { xs: 'center', md: 'flex-start' },
...theme.applyDarkStyles({
color: 'primary.300',
}),
}),
]}
>
<IconImage loading="eager" width={28} height={28} name="product-core" />{' '}
<Link href={ROUTES.productCore}>MUI Core</Link>{' '}
<Typography component="span" variant="inherit" sx={{ color: 'divider' }}>
/
</Typography>
<Typography component="span" variant="inherit" sx={{ color: 'text.primary' }}>
Material UI
</Typography>
</Typography>
<Typography variant="h1" sx={{ my: 2, maxWidth: 500 }}>
Ready to use <br />
<GradientText>Material Design</GradientText>
<br />
components
</Typography>
<Typography sx={{ color: 'text.secondary', mb: 3, maxWidth: 500 }}>
Material UI is an open-source React component library that implements Google&apos;s
Material Design. It&apos;s comprehensive and can be used in production out of the box.
</Typography>
<GetStartedButtons
primaryUrl={ROUTES.materialDocs}
secondaryLabel="View templates"
secondaryUrl={ROUTES.freeTemplates}
altInstallation="npm install @mui/material @emotion/react @emotion/styled"
/>
</Box>
}
rightSx={{
p: 3,
minWidth: 2000,
flexDirection: 'column',
overflow: 'hidden', // the components on the Hero section are mostly illustrative, even though they're interactive. That's why scrolling is disabled.
}}
right={
<CssVarsProvider theme={customTheme}>
<Paper sx={{ maxWidth: 780, p: 2, mb: 4 }}>
<Stepper activeStep={1}>
<Step>
<StepLabel>Search for React UI libraries</StepLabel>
</Step>
<Step>
<StepLabel>Spot Material UI</StepLabel>
</Step>
<Step>
<StepLabel>Choose Material UI</StepLabel>
</Step>
</Stepper>
</Paper>
<Box
sx={{
'& > div': {
width: 370,
display: 'inline-flex',
verticalAlign: 'top',
},
}}
>
<Stack spacing={4} useFlexGap>
<div>
<Accordion
elevation={0}
variant="outlined"
defaultExpanded
disableGutters
sx={{ borderBottom: 0 }}
>
<AccordionSummary
expandIcon={<ExpandMoreRoundedIcon fontSize="small" />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography variant="body2">Usage</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2">
Material UI components work in isolation. They are self-contained, and will
only inject the styles they need to display.
</Typography>
</AccordionDetails>
</Accordion>
<Accordion elevation={0} variant="outlined" disableGutters>
<AccordionSummary
expandIcon={<ExpandMoreRoundedIcon fontSize="small" />}
aria-controls="panel2a-content"
id="panel2a-header"
>
<Typography variant="body2">Globals</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2">
Material UI understands a handful of important globals that you&apos;ll need
to be aware of.
</Typography>
</AccordionDetails>
</Accordion>
<Accordion disabled elevation={0} disableGutters>
<AccordionSummary
expandIcon={<ExpandMoreRoundedIcon fontSize="small" />}
aria-controls="panel3a-content"
id="panel3a-header"
>
<Typography variant="body2">Secret Files</Typography>
</AccordionSummary>
</Accordion>
</div>
<Alert variant="filled" color="info" icon={<CheckCircleRounded fontSize="small" />}>
Check Material UI out now!
</Alert>
<SwitchToggleDemo />
<TabsDemo />
<Paper elevation={0} variant="outlined" sx={{ overflow: 'hidden' }}>
<List sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
<ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
primary="Brunch this weekend?"
secondary={
<React.Fragment>
<Typography
component="span"
variant="body2"
sx={{ color: 'text.primary', display: 'inline' }}
>
Michael Scott
</Typography>
{" — I'll be in your neighborhood doing errands this…"}
</React.Fragment>
}
/>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Travis Howard" src="/static/images/avatar/2.jpg" />
</ListItemAvatar>
<ListItemText
primary="Summer BBQ"
secondary={
<React.Fragment>
<Typography
component="span"
variant="body2"
sx={{ color: 'text.primary', display: 'inline' }}
>
to Jim, Pam and Ryan
</Typography>
{" — Wish I could come, but I'm out of town this…"}
</React.Fragment>
}
/>
</ListItem>
</List>
</Paper>
</Stack>
<Stack
spacing={4}
useFlexGap
sx={{ ml: 4, '& > .MuiPaper-root': { maxWidth: 'none' } }}
>
<Box sx={{ display: 'flex', gap: 2, '& button': { textWrap: 'nowrap' } }}>
<Button variant="contained" startIcon={<DownloadIcon fontSize="small" />} fullWidth>
Install library
</Button>
<Button variant="outlined" startIcon={<DownloadIcon fontSize="small" />} fullWidth>
Install library
</Button>
</Box>
<Paper elevation={0} variant="outlined" sx={{ p: 2 }}>
<Typography
id="temperature-slider"
component="div"
variant="subtitle2"
sx={{ mb: 1, fontWeight: 400 }}
>
Temperature range
</Typography>
<SlideDemo />
</Paper>
<TextField
id="core-hero-input"
defaultValue="Material UI"
label="Component library"
/>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
gap: 2,
}}
>
<BadgeVisibilityDemo />
<Paper
variant="outlined"
elevation={0}
sx={{
width: '100%',
py: 2,
px: 2,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Rating name="half-rating" defaultValue={2.5} precision={0.5} />
</Paper>
</Box>
<Card sx={{ maxWidth: 345 }}>
<CardHeader
avatar={
<Avatar
sx={{ bgcolor: 'primary.50', color: 'primary.600', fontWeight: 'bold' }}
>
YN
</Avatar>
}
title="Yosemite National Park"
subheader="California, United States"
/>
<CardMedia
height={125}
alt=""
component="img"
image="/static/images/cards/yosemite.jpeg"
/>
<CardContent sx={{ pb: 0 }}>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Not just a great valley, but a shrine to human foresight, the strength of
granite, the power of glaciers, the persistence of life, and the tranquility of
the High Sierra.
</Typography>
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteBorderRounded fontSize="small" />
</IconButton>
<IconButton aria-label="share">
<ShareRounded fontSize="small" />
</IconButton>
<IconButton aria-label="share" sx={{ ml: 'auto' }}>
<RateReviewOutlined fontSize="small" />
</IconButton>
</CardActions>
</Card>
</Stack>
</Box>
</CssVarsProvider>
}
/>
);
}

View File

@@ -0,0 +1,248 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import DevicesOtherRoundedIcon from '@mui/icons-material/DevicesOtherRounded';
import SwitchAccessShortcutRoundedIcon from '@mui/icons-material/SwitchAccessShortcutRounded';
import DragHandleRounded from '@mui/icons-material/DragHandleRounded';
import StyleRoundedIcon from '@mui/icons-material/StyleRounded';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import Frame from 'docs/src/components/action/Frame';
import RealEstateCard from 'docs/src/components/showcase/RealEstateCard';
import FlashCode from 'docs/src/components/animation/FlashCode';
import useResizeHandle from 'docs/src/modules/utils/useResizeHandle';
const code = `
<Card
variant="outlined"
sx={{ p: 2, display: 'flex', flexWrap: 'wrap', zIndex: 1 }}
>
<CardMedia
component="img"
width="100"
height="100"
alt="123 Main St, Phoenix, AZ cover"
src="/images/real-estate.png"
sx={{
borderRadius: '6px',
width: { xs: '100%', sm: 100 },
}}
/>
<Box sx={{ alignSelf: 'center', ml: 2 }}>
<Typography variant="body2" color="text.secondary" fontWeight="regular">
123 Main St, Phoenix, AZ, USA
</Typography>
<Typography fontWeight="bold" noWrap gutterBottom>
$280k - $310k
</Typography>
<Chip
size="small"
variant="outlined"
icon={<InfoRounded />}
label="Confidence score: 85%"
sx={(theme) => ({
'.MuiChip-icon': { fontSize: 16, ml: '4px', color: 'success.500' },
bgcolor: 'success.50',
borderColor: 'success.100',
color: 'success.900',
...theme.applyDarkStyles({
bgcolor: 'primaryDark.700',
color: 'success.200',
borderColor: 'success.900',
}),
})}
/>
</Box>
</Card>`;
const startLine = [27, 15, 12];
const endLine = [37, 20, 12];
const scrollTo = [27, 10, 4];
export default function MaterialStyling() {
const [index, setIndex] = React.useState(0);
const objectRef = React.useRef<HTMLDivElement>(null);
const { dragging, getDragHandlers } = useResizeHandle(objectRef, { minWidth: '253px' });
const infoRef = React.useRef<HTMLDivElement>(null);
const getSelectedProps = (i: number) => ({
selected: index === i,
sx: { '& svg': { opacity: index === i ? 1 : 0.5 } },
});
React.useEffect(() => {
// 18px line-height
// 16px margin-top
// 1px border-width
infoRef.current!.scroll({ top: scrollTo[index] * 18 + 16 - 1, behavior: 'smooth' });
objectRef.current!.style.setProperty('width', '100%');
}, [index]);
return (
<Section>
<Grid container spacing={2}>
<Grid sx={{ minWidth: 0 }} size={{ md: 6 }}>
<SectionHeadline
overline="Styling"
title={
<Typography variant="h2">
Rapidly add and tweak any styles using <GradientText>CSS utilities</GradientText>
</Typography>
}
description="CSS utilities allow you to move faster and make for a smooth developer experience when styling any component."
/>
<Group sx={{ m: -2, p: 2 }}>
<Highlighter disableBorder {...getSelectedProps(0)} onClick={() => setIndex(0)}>
<Item
icon={<StyleRoundedIcon color="primary" />}
title="Leverage the tokens from your theme"
description="Easily use the design tokens defined in your theme for any CSS property out there."
/>
</Highlighter>
<Highlighter disableBorder {...getSelectedProps(1)} onClick={() => setIndex(1)}>
<Item
icon={<SwitchAccessShortcutRoundedIcon color="primary" />}
title="No context switching"
description="The styling and component usage are both in the same place, right where you need them."
/>
</Highlighter>
<Highlighter disableBorder {...getSelectedProps(2)} onClick={() => setIndex(2)}>
<Item
icon={<DevicesOtherRoundedIcon color="primary" />}
title="Responsive styles right inside system prop"
description="An elegant API for writing CSS media queries that match your theme breakpoints."
/>
</Highlighter>
</Group>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Frame sx={{ height: '100%' }}>
<Frame.Demo sx={{ overflow: 'auto' }}>
<Box
ref={objectRef}
style={{ touchAction: dragging ? 'none' : 'auto' }}
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
p: { xs: 2, sm: 5 },
pr: { xs: 2, sm: 3 },
minHeight: index === 2 ? 280 : 'initial',
backgroundColor: 'transparent',
}}
>
{index === 2 && (
<React.Fragment>
<Box
sx={[
{
cursor: 'col-resize',
display: 'flex',
alignItems: 'center',
position: 'absolute',
right: 0,
top: 0,
height: '100%',
color: 'grey.500',
'&:hover': {
color: 'grey.700',
},
},
(theme) =>
theme.applyDarkStyles({
color: 'grey.500',
'&:hover': {
color: 'grey.300',
},
}),
]}
{...getDragHandlers()}
>
<DragHandleRounded sx={{ transform: 'rotate(90deg)' }} />
</Box>
<Box
sx={(theme) => ({
pointerEvents: 'none',
width: '1px',
bgcolor: 'grey.200',
position: 'absolute',
left: { xs: 335, sm: 375 },
height: '100%',
...theme.applyDarkStyles({
bgcolor: 'divider',
}),
})}
>
<Box
sx={(theme) => ({
position: 'absolute',
bottom: 5,
typography: 'caption',
fontFamily: 'code',
left: -30,
color: 'text.secondary',
borderRadius: '4px',
bgcolor: 'grey.50',
border: '1px solid',
borderColor: 'grey.200',
px: 0.5,
...theme.applyDarkStyles({
bgcolor: 'primaryDark.700',
borderColor: 'primaryDark.600',
}),
})}
>
xs
</Box>
<Box
sx={(theme) => ({
position: 'absolute',
bottom: 5,
typography: 'caption',
fontFamily: 'code',
left: 7,
color: 'text.secondary',
borderRadius: '4px',
bgcolor: 'grey.50',
border: '1px solid',
borderColor: 'grey.200',
px: 0.5,
...theme.applyDarkStyles({
bgcolor: 'primaryDark.700',
borderColor: 'primaryDark.600',
}),
})}
>
sm
</Box>
</Box>
</React.Fragment>
)}
<RealEstateCard sx={{ width: '100%', maxWidth: 343 }} />
</Box>
</Frame.Demo>
<Frame.Info
ref={infoRef}
sx={{
maxHeight: index === 2 ? 282 : 400,
overflow: 'auto',
}}
>
<Box sx={{ position: 'relative', display: 'inline-block', minWidth: '100%' }}>
<HighlightedCode copyButtonHidden plainStyle code={code} language="jsx" />
<FlashCode startLine={startLine[index]} endLine={endLine[index]} />
</Box>
</Frame.Info>
</Frame>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,332 @@
import * as React from 'react';
import SwipeableViews from 'react-swipeable-views';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import ButtonBase, { ButtonBaseProps } from '@mui/material/ButtonBase';
import Typography from '@mui/material/Typography';
import LaunchRounded from '@mui/icons-material/LaunchRounded';
import DashboardRounded from '@mui/icons-material/DashboardRounded';
import Layers from '@mui/icons-material/Layers';
import ShoppingBag from '@mui/icons-material/ShoppingBag';
import KeyboardArrowLeftRounded from '@mui/icons-material/KeyboardArrowLeftRounded';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import { Link } from '@mui/docs/Link';
import More from 'docs/src/components/action/More';
export const DEMOS = ['Dashboard', 'Landing Pages', 'E-commerce'];
export const icons = {
[DEMOS[0]]: <DashboardRounded fontSize="small" />,
[DEMOS[1]]: <Layers fontSize="small" />,
[DEMOS[2]]: <ShoppingBag fontSize="small" />,
};
export const TEMPLATES = {
[DEMOS[0]]: [
{
name: 'Devias Kit Pro - Client & Admin Dashboard',
author: 'Devias',
src: {
light: '/static/branding/store-templates/template-4light.jpg',
dark: '/static/branding/store-templates/template-4dark.jpg',
},
href: 'https://mui.com/store/items/devias-kit-pro/',
},
{
name: 'Minimal - Client & Admin Dashboard',
author: 'Minimal',
src: {
light: '/static/branding/store-templates/template-1light.jpg',
dark: '/static/branding/store-templates/template-1dark.jpg',
},
href: 'https://mui.com/store/items/minimal-dashboard/',
},
{
name: 'Berry - React Material Admin Dashboard Template',
author: 'CodedThemes',
src: {
light: '/static/branding/store-templates/template-5light.jpg',
dark: '/static/branding/store-templates/template-5dark.jpg',
},
href: 'https://mui.com/store/items/berry-react-material-admin/',
},
{
name: 'Mira Pro - React Material Admin Dashboard',
author: 'Bootlab',
src: {
light: '/static/branding/store-templates/template-3light.jpg',
dark: '/static/branding/store-templates/template-3dark.jpg',
},
href: 'https://mui.com/store/items/mira-pro-react-material-admin-dashboard/',
},
],
[DEMOS[1]]: [
{
name: 'theFront - Multipurpose Template + UI Kit',
author: 'Maccarian',
src: {
light: '/static/branding/store-templates/template-2light.jpg',
dark: '/static/branding/store-templates/template-2dark.jpg',
},
href: 'https://mui.com/store/items/the-front-landing-page/',
},
{
name: 'Webbee Multipurpose Landing Page UI Kit',
author: 'Maccarian',
src: {
light: '/static/branding/store-templates/template-6light.jpg',
dark: '/static/branding/store-templates/template-6dark.jpg',
},
href: 'https://mui.com/store/items/webbee-landing-page/',
},
],
[DEMOS[2]]: [
{
name: 'Bazaar Pro - Multipurpose React Ecommerce Template',
author: 'UI Lib',
src: {
light: '/static/branding/store-templates/template-bazar-light.jpg',
dark: '/static/branding/store-templates/template-bazar-dark.jpg',
},
href: 'https://mui.com/store/items/bazar-pro-react-ecommerce-template/',
},
],
};
function ActionArea(props: ButtonBaseProps) {
return (
<ButtonBase
{...props}
sx={[
(theme) => ({
width: { xs: 70, sm: 48 },
height: { xs: 70, sm: 48 },
position: 'absolute',
top: 'calc(50% - 50px)',
p: 1.5,
color: (theme.vars || theme).palette.primary[500],
bgcolor: '#FFF',
border: '1px solid',
borderColor: (theme.vars || theme).palette.primary[200],
borderRadius: '50%',
boxShadow: `0 4px 12px ${alpha(theme.palette.grey[500], 0.2)}`,
transition: '0.2s',
'& > svg': { transition: '0.2s' },
'&.Mui-disabled': {
opacity: 0,
},
'&:hover, &:focus': {
'& > svg': { fontSize: 28 },
},
...theme.applyDarkStyles({
bgcolor: (theme.vars || theme).palette.primaryDark[900],
borderColor: (theme.vars || theme).palette.primary[900],
color: (theme.vars || theme).palette.primary[300],
boxShadow: `0 4px 12px ${alpha(theme.palette.common.black, 0.2)}`,
}),
}),
...(Array.isArray(props.sx) ? props.sx : [props.sx]),
]}
/>
);
}
export default function MaterialTemplates() {
const [demo, setDemo] = React.useState(DEMOS[0]);
const [templateIndex, setTemplateIndex] = React.useState(1);
const templates = TEMPLATES[demo];
return (
<Section bg="gradient" cozy>
<SectionHeadline
alwaysCenter
overline="Templates"
title={
<Typography variant="h2">
The right template for your
<br /> <GradientText>specific use case</GradientText>
</Typography>
}
description="A carefully curated collection of gorgeous, fully functional templates."
/>
<Group rowLayout desktopColumns={2} sx={{ p: 2 }}>
{DEMOS.map((name) => (
<Highlighter
key={name}
selected={name === demo}
onClick={() => {
setDemo(name);
setTemplateIndex(0);
}}
>
<Item
icon={React.cloneElement(icons[name], name === demo ? { color: 'primary' } : {})}
title={name}
smallerIconDistance
/>
</Highlighter>
))}
<More
component={Link}
href="https://mui.com/store/?utm_source=marketing&utm_medium=referral&utm_campaign=material-templates-cta2#populars"
noLinkStyle
/>
</Group>
<Box
sx={{
position: 'relative',
mt: 3,
minHeight: { xs: 240, sm: 320 },
height: { xs: 260, sm: 400, md: 500 },
mx: { xs: -2, sm: 0 },
}}
>
<Box
sx={{
position: 'absolute',
left: 0,
right: 0,
top: '50%',
py: 2,
transform: 'translate(0px, -50%)',
'& > div': { px: '12%', overflow: 'unset !important' },
'& .react-swipeable-view-container > div': {
overflow: 'unset !important',
},
}}
>
<SwipeableViews
springConfig={{
duration: '0.6s',
delay: '0s',
easeFunction: 'cubic-bezier(0.15, 0.3, 0.25, 1)',
}}
index={templateIndex}
resistance
enableMouseEvents
onChangeIndex={(index) => setTemplateIndex(index)}
>
{templates.map((item, index) => (
<Box
key={item.name}
sx={(theme) => ({
overflow: 'auto',
borderRadius: 1,
height: { xs: 220, sm: 320, md: 500 },
backgroundImage: `url(${item.src.light})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
border: '1px solid',
borderColor: templateIndex === index ? 'primary.100' : 'divider',
boxShadow:
templateIndex === index
? `0px 2px 12px ${alpha(theme.palette.primary[200], 0.3)}`
: undefined,
transition: '0.6s cubic-bezier(0.15, 0.3, 0.25, 1)',
transform: templateIndex !== index ? 'scale(0.92)' : 'scale(1)',
opacity: templateIndex === index ? 1 : 0.2,
...theme.applyDarkStyles({
backgroundImage: `url(${item.src.dark})`,
borderColor: templateIndex === index ? 'primary.900' : 'divider',
boxShadow:
templateIndex === index
? `0px 2px 8px ${alpha(theme.palette.primary[900], 0.4)}`
: undefined,
}),
})}
>
<Link
href={`${item.href}?utm_source=marketing&utm_medium=referral&utm_campaign=templates-cta2`}
noLinkStyle
target="_blank"
sx={[
(theme) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
gap: 1,
transition: '0.2s',
position: 'absolute',
width: '100%',
height: '100%',
opacity: 0,
top: 0,
left: 0,
bgcolor: alpha(theme.palette.primary[50], 0.6),
backdropFilter: 'blur(4px)',
textDecoration: 'none',
'&:hover, &:focus': {
opacity: 1,
},
...theme.applyDarkStyles({
bgcolor: alpha(theme.palette.primaryDark[900], 0.6),
}),
}),
]}
>
<Typography
variant="body2"
sx={{ color: 'text.tertiary', fontWeight: 'semiBold', textAlign: 'center' }}
>
Developed by {templates[templateIndex].author}
</Typography>
<Typography
component="p"
variant="h6"
sx={{ fontWeight: 'semiBold', textAlign: 'center', color: 'text.primary' }}
>
{templates[templateIndex].name}
</Typography>
<Box
sx={[
(theme) => ({
display: 'flex',
alignItems: 'center',
gap: 0.5,
color: 'primary.500',
...theme.applyDarkStyles({
color: 'primary.200',
}),
}),
]}
>
<Typography sx={{ fontWeight: 'bold' }}>Buy now</Typography>
<LaunchRounded fontSize="small" />
</Box>
</Link>
</Box>
))}
</SwipeableViews>
{templates.length > 1 && (
<React.Fragment>
<ActionArea
aria-label="Previous template"
disabled={templateIndex === 0}
onClick={() => setTemplateIndex((current) => Math.max(0, current - 1))}
sx={{ left: 0, transform: 'translate(-50%)', justifyContent: 'flex-end' }}
>
<KeyboardArrowLeftRounded />
</ActionArea>
<ActionArea
aria-label="Next template"
disabled={templateIndex === templates.length - 1}
onClick={() =>
setTemplateIndex((current) => Math.min(templates.length - 1, current + 1))
}
sx={{ right: 0, transform: 'translate(50%)', justifyContent: 'flex-start' }}
>
<KeyboardArrowRightRounded />
</ActionArea>
</React.Fragment>
)}
</Box>
</Box>
</Section>
);
}

View File

@@ -0,0 +1,135 @@
import * as React from 'react';
import { CssVarsProvider } from '@mui/material/styles';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import AutoAwesomeRounded from '@mui/icons-material/AutoAwesomeRounded';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import SvgMaterialDesign from 'docs/src/icons/SvgMaterialDesign';
import Frame from 'docs/src/components/action/Frame';
import PlayerCard from 'docs/src/components/showcase/PlayerCard';
const code = `
<Card
variant="outlined"
sx={{ p: 2,
width: { xs: '100%', sm: 'auto' },
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
alignItems: 'center',
gap: 2,
}}
>
<CardMedia
component="img"
width="100"
height="100"
alt="Contemplative Reptile album cover"
src="/images/contemplative-reptile.jpg"
sx={{ width: { xs: '100%', sm: 100 },
}}
/>
<Stack direction="column" alignItems="center" spacing={1} useFlexGap>
<div>
<Typography color="text.primary" fontWeight="semiBold">
Contemplative Reptile
</Typography>
<Typography
variant="caption"
color="text.secondary"
fontWeight="medium"
textAlign="center"
sx={{ width: '100%' }}
>
Sounds of Nature
</Typography>
</div>
<Stack direction="row" alignItems="center" spacing={1} useFlexGap>
<IconButton aria-label="Shuffle" disabled size="small">
<ShuffleRoundedIcon fontSize="small" />
</IconButton>
<IconButton aria-label="Fast rewind" disabled size="small">
<FastRewindRounded fontSize="small" />
</IconButton>
<IconButton
aria-label={paused ? 'Play music' : 'Pause music'}
onClick={() => setPaused((val) => !val)}
sx={{ mx: 1 }}
>
{paused ? <PlayArrowRounded /> : <PauseRounded />}
</IconButton>
<IconButton aria-label="Fast forward" disabled size="small">
<FastForwardRounded fontSize="small" />
</IconButton>
<IconButton aria-label="Loop music" disabled size="small">
<LoopRoundedIcon fontSize="small" />
</IconButton>
</Stack>
</Stack>
</Card>`;
export default function MaterialTheming() {
const [customized, setCustomized] = React.useState(true);
return (
<Section>
<Grid container spacing={2}>
<Grid sx={{ minWidth: 0 }} size={{ md: 6 }}>
<SectionHeadline
overline="Theming"
title={
<Typography variant="h2">
Build <GradientText>your design system</GradientText> just as you want it to be
</Typography>
}
description="Start quickly with Material Design or use the advanced theming feature to easily tailor the components to your needs."
/>
<Group sx={{ m: -2, p: 2 }}>
<Highlighter disableBorder selected={customized} onClick={() => setCustomized(true)}>
<Item
icon={<AutoAwesomeRounded color="warning" />}
title="Custom Theme"
description="Theming allows you to use your brand's design tokens, easily making the components reflect its look and feel."
/>
</Highlighter>
<Highlighter disableBorder selected={!customized} onClick={() => setCustomized(false)}>
<Item
icon={<SvgMaterialDesign />}
title="Material Design"
description="Every component comes with Google's tried and tested design system ready for use."
/>
</Highlighter>
</Group>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Frame sx={{ height: '100%' }}>
<Frame.Demo
sx={{
p: 2,
flexGrow: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: 188,
}}
>
{customized ? (
<PlayerCard />
) : (
<CssVarsProvider>
<PlayerCard disableTheming />
</CssVarsProvider>
)}
</Frame.Demo>
<Frame.Info sx={{ maxHeight: 300, overflow: 'auto' }}>
<HighlightedCode copyButtonHidden plainStyle code={code} language="jsx" />
</Frame.Info>
</Frame>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,222 @@
import * as React from 'react';
import SwipeableViews from 'react-swipeable-views';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import ButtonBase, { ButtonBaseProps } from '@mui/material/ButtonBase';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import LaunchRounded from '@mui/icons-material/LaunchRounded';
import KeyboardArrowLeftRounded from '@mui/icons-material/KeyboardArrowLeftRounded';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import Frame from 'docs/src/components/action/Frame';
import { Link } from '@mui/docs/Link';
import More from 'docs/src/components/action/More';
import { DEMOS, icons, TEMPLATES } from 'docs/src/components/productMaterial/MaterialTemplates';
function ActionArea(props: ButtonBaseProps) {
return (
<ButtonBase
{...props}
sx={[
(theme) => ({
width: 100,
height: 100,
borderRadius: '50%',
transition: '0.2s',
'&.Mui-disabled': {
opacity: 0,
},
'& > svg': { transition: '0.2s' },
backdropFilter: 'blur(4px)',
bgcolor: alpha(theme.palette.primaryDark[500], 0.5),
'&:hover, &:focus': {
'& > svg': { fontSize: 28 },
},
position: 'absolute',
top: 'calc(50% - 50px)',
color: '#fff',
p: 1.5,
...theme.applyDarkStyles({
bgcolor: alpha(theme.palette.primary[500], 0.5),
}),
}),
...(Array.isArray(props.sx) ? props.sx : [props.sx]),
]}
/>
);
}
export default function TemplateDemo() {
const [demo, setDemo] = React.useState(DEMOS[0]);
const [templateIndex, setTemplateIndex] = React.useState(0);
const templates = TEMPLATES[demo];
return (
<Section bg="gradient">
<Grid container spacing={2} alignItems="center">
<Grid sx={{ minWidth: 0 }} size={{ md: 6 }}>
<SectionHeadline
overline="Templates"
title={
<Typography variant="h2">
The right template for your <GradientText>specific use case</GradientText>
</Typography>
}
description="The Material UI collection of templates offers an expanding list of use cases designed to support projects of various types."
/>
<Group desktopColumns={2} sx={{ m: -2, p: 2 }}>
{DEMOS.map((name) => (
<Highlighter
key={name}
selected={name === demo}
onClick={() => {
setDemo(name);
setTemplateIndex(0);
}}
>
<Item
icon={React.cloneElement(icons[name], name === demo ? { color: 'primary' } : {})}
title={name}
/>
</Highlighter>
))}
<More
component={Link}
href="https://mui.com/store/?utm_source=marketing&utm_medium=referral&utm_campaign=templates-cta2#populars"
noLinkStyle
/>
</Group>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Frame>
<Frame.Demo sx={{ minHeight: { xs: 240, sm: 320 } }}>
<Box
sx={{
overflow: 'hidden',
position: 'absolute',
left: 0,
right: 0,
top: '50%',
py: 2,
transform: 'translate(0px, -50%)',
'& > div': { px: '12%', overflow: 'unset !important' },
'& .react-swipeable-view-container > div': {
overflow: 'unset !important',
},
}}
>
<SwipeableViews
springConfig={{
duration: '0.6s',
delay: '0s',
easeFunction: 'cubic-bezier(0.15, 0.3, 0.25, 1)',
}}
index={templateIndex}
resistance
enableMouseEvents
onChangeIndex={(index) => setTemplateIndex(index)}
>
{templates.map((item, index) => (
<Box
key={item.name}
sx={(theme) => ({
borderRadius: 1,
height: { xs: 200, sm: 240 },
backgroundImage: `url(${item.src.light})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
bgcolor: 'background.paper',
boxShadow: '0px 4px 10px rgba(61, 71, 82, 0.25)',
transition: '0.6s cubic-bezier(0.15, 0.3, 0.25, 1)',
transform: templateIndex !== index ? 'scale(0.92)' : 'scale(1)',
...theme.applyDarkStyles({
backgroundImage: `url(${item.src.dark})`,
boxShadow: '0px 4px 10px rgba(0, 0, 0, 0.6)',
}),
})}
>
<Link
href={`${item.href}?utm_source=marketing&utm_medium=referral&utm_campaign=templates-cta2`}
noLinkStyle
target="_blank"
sx={{
transition: '0.3s',
borderRadius: 1,
position: 'absolute',
width: '100%',
height: '100%',
opacity: 0,
top: 0,
left: 0,
bgcolor: (theme) => alpha(theme.palette.primaryDark[900], 0.4),
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&:hover, &:focus': {
opacity: 1,
},
}}
>
<Typography sx={{ fontWeight: 'bold' }}>Go to store</Typography>
<LaunchRounded fontSize="small" sx={{ ml: 1 }} />
</Link>
</Box>
))}
</SwipeableViews>
{templates.length > 1 && (
<React.Fragment>
<ActionArea
aria-label="Previous template"
disabled={templateIndex === 0}
onClick={() => setTemplateIndex((current) => Math.max(0, current - 1))}
sx={{ left: 0, transform: 'translate(-50%)', justifyContent: 'flex-end' }}
>
<KeyboardArrowLeftRounded />
</ActionArea>
<ActionArea
aria-label="Next template"
disabled={templateIndex === templates.length - 1}
onClick={() =>
setTemplateIndex((current) => Math.min(templates.length - 1, current + 1))
}
sx={{ right: 0, transform: 'translate(50%)', justifyContent: 'flex-start' }}
>
<KeyboardArrowRightRounded />
</ActionArea>
</React.Fragment>
)}
</Box>
</Frame.Demo>
<Frame.Info
sx={{
width: '100%',
display: 'flex',
alignItems: 'start',
justifyContent: 'space-between',
'& .MuiIconButton-root': { display: { xs: 'none', md: 'inline-flex' } },
}}
>
<div>
<Typography variant="body2" noWrap sx={{ fontWeight: 'medium', mb: 0.5 }}>
{templates[templateIndex].name}
</Typography>
<Typography variant="body2" noWrap sx={{ color: 'grey.500', fontSize: '0.75rem' }}>
Developed by {templates[templateIndex].author}
</Typography>
</div>
<Typography variant="caption" sx={{ color: 'grey.500' }}>
{templateIndex + 1} / {templates.length}
</Typography>
</Frame.Info>
</Frame>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,98 @@
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import GradientText from 'docs/src/components/typography/GradientText';
import HeroContainer from 'docs/src/layouts/HeroContainer';
import IconImage from 'docs/src/components/icon/IconImage';
import { Link } from '@mui/docs/Link';
import {
StoreTemplatesSet1,
StoreTemplatesSet2,
} from 'docs/src/components/home/StoreTemplatesBanner';
export default function TemplateHero() {
return (
<HeroContainer
linearGradient
left={
<Box sx={{ textAlign: { xs: 'center', md: 'left' } }}>
<Typography
variant="body2"
sx={[
{
fontWeight: 'bold',
},
(theme) => ({
display: 'flex',
alignItems: 'center',
justifyContent: { xs: 'center', md: 'start' },
'& > *': { mr: 1 },
color: 'primary.600',
...theme.applyDarkStyles({
color: 'primary.400',
}),
}),
]}
>
<IconImage width={28} height={28} loading="eager" name="product-templates" /> Templates
</Typography>
<Typography variant="h1" sx={{ my: 2, maxWidth: 500 }}>
<GradientText>Beautiful and fully built</GradientText> Material&nbsp;UI templates
</Typography>
<Typography sx={{ color: 'text.secondary', mb: 3, maxWidth: 500 }}>
A collection of 4.5 average rating templates, selected and curated by Material UI&apos;s
maintainers to get your projects up and running today.
</Typography>
<Button
component={Link}
noLinkStyle
href="https://mui.com/store/?utm_source=marketing&utm_medium=referral&utm_campaign=templates-cta#populars"
variant="contained"
endIcon={<KeyboardArrowRightRounded />}
sx={{ width: { xs: '100%', sm: 'auto' } }}
>
Browse templates
</Button>
</Box>
}
right={
<Box sx={{ position: 'relative', height: '100%', perspective: '1000px' }}>
<Box
sx={{
left: '40%',
position: 'absolute',
display: 'flex',
transform: 'translateX(-40%) rotateZ(-30deg) rotateX(8deg) rotateY(8deg)',
transformOrigin: 'center center',
}}
>
<StoreTemplatesSet1
disableLink
keyframes={{
'0%': {
transform: 'translateY(-200px)',
},
'100%': {
transform: 'translateY(-40px)',
},
}}
/>
<StoreTemplatesSet2
disableLink
keyframes={{
'0%': {
transform: 'translateY(150px)',
},
'100%': {
transform: 'translateY(40px)',
},
}}
sx={{ ml: { xs: 2, sm: 4, md: 8 } }}
/>
</Box>
</Box>
}
/>
);
}

View File

@@ -0,0 +1,59 @@
import Paper from '@mui/material/Paper';
import { BarChart } from '@mui/x-charts/BarChart';
import { blueberryTwilightPaletteLight } from '@mui/x-charts';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import Frame from 'docs/src/components/action/Frame';
const code = `
<BarChart
series={[
{ data: [35, 44, 24, 34] },
{ data: [51, 6, 49, 30] },
{ data: [15, 25, 30, 50] },
{ data: [60, 50, 15, 25] },
]}
height={290}
xAxis={[{ data: ['Q1', 'Q2', 'Q3', 'Q4'], scaleType: 'band' }]}
margin={{ top: 10, bottom: 30, left: 40, right: 10 }}
colors={blueberryTwilightPaletteLight}
/>`;
export default function XChartsDemo() {
return (
<Frame sx={{ height: '100%' }}>
<Frame.Demo sx={{ p: 2 }}>
<Paper
variant="outlined"
sx={(theme) => ({
p: 2,
display: 'flex',
alignItems: 'center',
maxWidth: '100%',
mx: 'auto',
bgcolor: '#FFF',
borderRadius: '8px',
...theme.applyDarkStyles({
bgcolor: 'primaryDark.900',
}),
})}
>
<BarChart
series={[
{ data: [35, 44, 24, 34] },
{ data: [51, 6, 49, 30] },
{ data: [15, 25, 30, 50] },
{ data: [60, 50, 15, 25] },
]}
height={290}
xAxis={[{ data: ['Q1', 'Q2', 'Q3', 'Q4'], scaleType: 'band' }]}
margin={{ top: 10, bottom: 0, left: 0, right: 10 }}
colors={blueberryTwilightPaletteLight}
/>
</Paper>
</Frame.Demo>
<Frame.Info data-mui-color-scheme="dark" sx={{ maxHeight: 300, overflow: 'auto' }}>
<HighlightedCode copyButtonHidden plainStyle code={code} language="jsx" />
</Frame.Info>
</Frame>
);
}

View File

@@ -0,0 +1,47 @@
import * as React from 'react';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import XComponentsSwitcher from 'docs/src/components/productX/XComponentsSwitcher';
import XGridFullDemo from 'docs/src/components/productX/XGridFullDemo';
import XDateRangeDemo from 'docs/src/components/productX/XDateRangeDemo';
import XTreeViewDemo from 'docs/src/components/productX/XTreeViewDemo';
import XChartsDemo from 'docs/src/components/productX/XChartsDemo';
export default function XComponents() {
const [componentIndex, setComponentIndex] = React.useState(0);
return (
<Section bg="gradient">
<Grid container spacing={2}>
<Grid size={{ md: 6 }}>
<SectionHeadline
overline="Advanced React component library"
title={
<Typography variant="h2">
Powerful components for <GradientText>advanced use cases</GradientText>
</Typography>
}
description="The MUI X packages can power complex and data-intensive applications across a wide spectrum of use cases."
/>
<XComponentsSwitcher
componentIndex={componentIndex}
setComponentIndex={setComponentIndex}
/>
</Grid>
<Grid
sx={componentIndex === 0 ? { minHeight: { xs: 'auto', sm: 757, md: 'unset' } } : {}}
size={{ xs: 12, md: 6 }}
>
<React.Fragment>
{componentIndex === 0 && <XGridFullDemo />}
{componentIndex === 1 && <XDateRangeDemo />}
{componentIndex === 2 && <XChartsDemo />}
{componentIndex === 3 && <XTreeViewDemo />}
</React.Fragment>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,165 @@
import * as React from 'react';
import dynamic from 'next/dynamic';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded';
import PivotTableChartRoundedIcon from '@mui/icons-material/PivotTableChartRounded';
import CalendarMonthRoundedIcon from '@mui/icons-material/CalendarMonthRounded';
import AccountTreeRounded from '@mui/icons-material/AccountTreeRounded';
import BarChartRoundedIcon from '@mui/icons-material/BarChartRounded';
import { visuallyHidden } from '@mui/utils';
import Highlighter from 'docs/src/components/action/Highlighter';
import { Link } from '@mui/docs/Link';
import ROUTES from 'docs/src/route';
const SwipeableViews = dynamic(() => import('react-swipeable-views'), { ssr: false });
function ComponentItem({
label,
icon,
name,
description,
href,
}: {
label: string;
icon: React.ReactNode;
name: React.ReactNode;
description?: React.ReactNode;
href: string;
}) {
return (
<Box
component="span"
sx={{
flexGrow: 1,
display: 'flex',
p: 2,
flexDirection: { xs: 'column', md: 'row' },
alignItems: { md: 'center' },
gap: 2.5,
}}
>
{icon}
<div>
<Typography
component="span"
variant="body2"
sx={{ color: 'text.primary', fontWeight: 'bold', display: 'block' }}
>
{name}
</Typography>
{description && (
<Typography
component="span"
variant="body2"
gutterBottom
sx={{ color: 'text.secondary', fontWeight: 'regular', display: 'block' }}
>
{description}
</Typography>
)}
<Link
href={href}
variant="body2"
onClick={(event: React.MouseEvent<HTMLAnchorElement>) => {
event.stopPropagation();
}}
sx={{
color: 'primary',
display: 'inline-flex',
alignItems: 'center',
fontWeight: 'semiBold',
'& > svg': { transition: '0.2s' },
'&:hover > svg': { transform: 'translateX(2px)' },
}}
>
<span>View the docs</span>{' '}
<Box component="span" sx={visuallyHidden}>
{label}
</Box>
<KeyboardArrowRightRounded fontSize="small" sx={{ mt: '1px', ml: '2px' }} />
</Link>
</div>
</Box>
);
}
export default function XComponentsSwitcher(props: {
componentIndex: number;
setComponentIndex: React.Dispatch<React.SetStateAction<number>>;
}) {
const { componentIndex, setComponentIndex } = props;
const componentElement = [
<ComponentItem
name="Data Grid"
label="Fast, feature-rich data table."
description="Fast, feature-rich data table."
icon={<PivotTableChartRoundedIcon />}
href={ROUTES.dataGridOverview}
/>,
<ComponentItem
name="Date and Time Pickers"
description="A suite of components for selecting dates, times, and ranges."
label="A suite of components for selecting dates, times, and ranges."
icon={<CalendarMonthRoundedIcon />}
href={ROUTES.datePickersOverview}
/>,
<ComponentItem
name="Charts"
description="Data visualization graphs, including bar, line, pie, scatter, and more."
label="Data visualization graphs, including bar, line, pie, scatter, and more."
icon={<BarChartRoundedIcon />}
href={ROUTES.chartsOverview}
/>,
<ComponentItem
name="Tree View"
description="Display hierarchical data, such as a file system navigator."
label="Display hierarchical data, such as a file system navigator."
icon={<AccountTreeRounded />}
href={ROUTES.treeViewOverview}
/>,
];
return (
<React.Fragment>
<Box
sx={{ display: { md: 'none' }, maxWidth: 'calc(100vw - 40px)', '& > div': { pr: '32%' } }}
>
<SwipeableViews
index={componentIndex}
resistance
enableMouseEvents
onChangeIndex={(index) => setComponentIndex(index)}
>
{componentElement.map((element, index) => (
<Highlighter
key={index}
disableBorder
onClick={() => setComponentIndex(index)}
selected={componentIndex === index}
sx={{
width: '100%',
transition: '0.3s',
transform: componentIndex !== index ? 'scale(0.9)' : 'scale(1)',
}}
>
{element}
</Highlighter>
))}
</SwipeableViews>
</Box>
<Stack spacing={1} useFlexGap sx={{ display: { xs: 'none', md: 'flex' }, maxWidth: 500 }}>
{componentElement.map((element, index) => (
<Highlighter
key={index}
disableBorder
onClick={() => setComponentIndex(index)}
selected={componentIndex === index}
>
{element}
</Highlighter>
))}
</Stack>
</React.Fragment>
);
}

View File

@@ -0,0 +1,219 @@
import * as React from 'react';
import { DataGridPro, useGridApiRef } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import EditRoundedIcon from '@mui/icons-material/EditRounded';
import LibraryAddCheckRounded from '@mui/icons-material/LibraryAddCheckRounded';
import SortByAlphaRounded from '@mui/icons-material/SortByAlphaRounded';
import AutoStoriesOutlined from '@mui/icons-material/AutoStoriesOutlined';
import FilterAltRounded from '@mui/icons-material/FilterAltRounded';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import { Link } from '@mui/docs/Link';
import Section from 'docs/src/layouts/Section';
import SectionHeadline from 'docs/src/components/typography/SectionHeadline';
import GradientText from 'docs/src/components/typography/GradientText';
import Item, { Group } from 'docs/src/components/action/Item';
import Highlighter from 'docs/src/components/action/Highlighter';
import More from 'docs/src/components/action/More';
import Frame from 'docs/src/components/action/Frame';
import FlashCode from 'docs/src/components/animation/FlashCode';
import { ShowcaseCodeWrapper } from 'docs/src/components/home/ShowcaseContainer';
import XGridGlobalStyles from 'docs/src/components/home/XGridGlobalStyles';
import { AppearingInfoBox } from 'docs/src/components/action/MoreInfoBox';
import ROUTES from 'docs/src/route';
const DEMOS = ['Editing', 'Selection', 'Sorting', 'Pagination', 'Filtering'] as const;
const code = `<DataGrid
columns={[ // column definition example
{
field: 'name',
headerName: 'Name',
editable: true,
sortable: true,
filterable: true,
},
]}
checkboxSelection
disableRowSelectionOnClick
pagination
/>`;
const startLine = {
[DEMOS[0]]: 6,
[DEMOS[1]]: 11,
[DEMOS[2]]: 7,
[DEMOS[3]]: 13,
[DEMOS[4]]: 8,
};
const dataGridStyleOverrides = <XGridGlobalStyles selector="#data-grid-demo" pro />;
export default function XDataGrid() {
const [demo, setDemo] = React.useState<(typeof DEMOS)[number] | null>(null);
const gridApiRef = useGridApiRef();
const icons = {
[DEMOS[0]]: <EditRoundedIcon fontSize="small" />,
[DEMOS[1]]: <LibraryAddCheckRounded fontSize="small" />,
[DEMOS[2]]: <SortByAlphaRounded fontSize="small" />,
[DEMOS[3]]: <AutoStoriesOutlined fontSize="small" />,
[DEMOS[4]]: <FilterAltRounded fontSize="small" />,
};
const { loading, data } = useDemoData({
dataSet: 'Employee',
rowLength: 1000,
maxColumns: 5,
editable: true,
});
const firstRowId = data.rows[0]?.id;
React.useEffect(() => {
if (gridApiRef && gridApiRef.current && !loading) {
if (demo) {
gridApiRef.current.scroll({ top: 0, left: 0 });
}
if (demo === DEMOS[0]) {
document.body.focus();
setTimeout(() => {
const cell = document.querySelector(
'#data-grid-demo div[role="cell"][data-field="name"]',
);
if (cell) {
const clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent('dblclick', true, true);
cell.dispatchEvent(clickEvent);
}
}, 120);
}
if (demo === DEMOS[1]) {
const checkbox = document.querySelector(
'#data-grid-demo div[data-field="__check__"] input',
) as HTMLInputElement | null;
if (checkbox && !checkbox.checked) {
checkbox.click();
}
}
if (demo === DEMOS[2]) {
const sorter = document.querySelector(
'#data-grid-demo button[aria-label="Sort"]',
) as HTMLButtonElement | null;
if (sorter) {
sorter.click();
}
}
if (demo === DEMOS[3]) {
const nextPage = document.querySelector(
'#data-grid-demo button[aria-label="Go to next page"]',
) as HTMLButtonElement | null;
if (nextPage) {
nextPage.click();
}
}
if (demo === DEMOS[4]) {
document.body.focus();
gridApiRef.current.showFilterPanel('name');
}
}
}, [demo, loading, firstRowId, gridApiRef]);
return (
<Section cozy>
<Grid container spacing={2}>
<Grid sx={{ minWidth: 0 }} size={{ md: 6 }}>
<SectionHeadline
overline="Data Grid"
title={
<Typography variant="h2">
A level of <GradientText>performance and quality</GradientText> that hasn&apos;t
been seen before
</Typography>
}
description="The MUI X Data Grid is a data table powerhouse. It is packed with exclusive features that will enrich the experience of dealing with and maintaining lots of data."
/>
<Group desktopColumns={2} sx={{ m: -2, p: 2 }}>
{DEMOS.map((name) => (
<Highlighter key={name} selected={name === demo} onClick={() => setDemo(name)}>
<Item icon={icons[name]} title={name} />
</Highlighter>
))}
<More href={ROUTES.dataGridFeatures} />
</Group>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Paper
id="data-grid-demo"
variant="outlined"
sx={[
{
position: 'relative',
zIndex: 1,
height: 240,
borderRadius: '10px 10px 0 0',
'& .MuiDataGrid-root': {
'& .MuiAvatar-root': { width: 24, height: 24, fontSize: 14, fontWeight: 'bold' },
'& .MuiDataGrid-footerContainer': {
minHeight: 48,
borderTop: '1px solid',
borderColor: 'grey.200',
},
'& .MuiTablePagination-root': {
fontSize: '0.75rem',
'& p': {
fontSize: '0.75rem',
},
'& .MuiToolbar-root': {
minHeight: 48,
},
},
},
},
(theme) =>
theme.applyDarkStyles({
borderColor: 'divider',
'& .MuiDataGrid-root': {
'& .MuiDataGrid-footerContainer': {
borderColor: 'primaryDark.600',
},
},
}),
]}
>
{dataGridStyleOverrides}
<DataGridPro
{...data}
apiRef={gridApiRef}
loading={loading}
density="compact"
checkboxSelection
disableRowSelectionOnClick
pagination
/>
</Paper>
<Frame.Info sx={{ p: 0 }}>
<ShowcaseCodeWrapper maxHeight="100%" clip>
<HighlightedCode copyButtonHidden plainStyle code={code} language="jsx" />
{demo && <FlashCode startLine={startLine[demo]} sx={{ mx: 1 }} />}
<AppearingInfoBox appeared={demo === DEMOS[3] || demo === DEMOS[4]}>
<React.Fragment>
<Typography
variant="body2"
sx={{ color: 'grey.50', fontWeight: 'medium', mb: '4px' }}
>
{demo === DEMOS[3] && 'Pagination > 100 rows per page is a paid feature!'}
{demo === DEMOS[4] && 'Multi-column filtering is a paid feature!'}
</Typography>
<Typography variant="body2" sx={{ color: 'grey.300' }}>
The Data Grid and all other MUI X components are available on free and paid
plans. Find more details about each plan and its features are on{' '}
<Link href={ROUTES.pricing}>the pricing page</Link>.
</Typography>
</React.Fragment>
</AppearingInfoBox>
</ShowcaseCodeWrapper>
</Frame.Info>
</Grid>
</Grid>
</Section>
);
}

View File

@@ -0,0 +1,200 @@
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import { StaticDateRangePicker } from '@mui/x-date-pickers-pro/StaticDateRangePicker';
import {
PickersShortcutsItem,
PickersShortcutsProps,
DateRange,
useIsValidValue,
usePickerActionsContext,
} from '@mui/x-date-pickers-pro';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import dayjs, { Dayjs } from 'dayjs';
import Frame from 'docs/src/components/action/Frame';
const startDate = dayjs();
startDate.date(10);
const endDate = dayjs();
endDate.date(endDate.date() + 28);
function CustomRangeShortcuts(props: PickersShortcutsProps<DateRange<Dayjs>>) {
const { items, changeImportance = 'accept' } = props;
const isValid = useIsValidValue<DateRange<Dayjs>>();
const { setValue } = usePickerActionsContext<DateRange<Dayjs>>();
if (items == null || items.length === 0) {
return null;
}
const resolvedItems = items.map((item) => {
const newValue = item.getValue({ isValid });
return {
label: item.label,
onClick: () => {
setValue(newValue, { changeImportance, shortcut: item });
},
disabled: !isValid(newValue),
};
});
return (
<Box sx={{ gridRow: 1, gridColumn: '2 / 4' }}>
<List
sx={{
display: 'flex',
p: 1.5,
gap: 1.5,
'& .MuiListItem-root': {
p: 0,
width: 'fit-content',
},
}}
>
{resolvedItems.map((item) => {
return (
<ListItem key={item.label}>
<Chip size="small" {...item} />
</ListItem>
);
})}
</List>
<Divider />
</Box>
);
}
const code = `
<LocalizationProvider dateAdapter={AdapterDateFns}>
<StaticDateRangePicker
displayStaticWrapperAs="desktop"
value={[startDate, endDate]}
slots={{
shortcuts: CustomRangeShortcuts,
}}
slotProps={{
shortcuts: {
items: shortcutsItems,
},
}}
/>
</LocalizationProvider>`;
export default function XDateRangeDemo() {
const today = dayjs();
const shortcutsItems: PickersShortcutsItem<DateRange<Dayjs>>[] = [
{
label: 'This Week',
getValue: () => {
return [today.startOf('week'), today.endOf('week')];
},
},
{
label: 'Last Week',
getValue: () => {
const prevWeek = today.add(-7, 'days');
return [prevWeek.startOf('week'), prevWeek.endOf('week')];
},
},
{
label: 'Last 7 Days',
getValue: () => {
return [today.add(-7, 'days'), today];
},
},
{ label: 'Reset', getValue: () => [null, null] },
];
return (
<Frame>
<Frame.Demo sx={{ p: 2 }}>
<Paper
variant="outlined"
sx={[
{
borderRadius: '8px',
overflow: 'hidden',
'& > div': {
overflow: 'auto',
bgcolor: '#FFF',
},
'& > div > div > div > div': {
flexGrow: 1,
},
'& .MuiTypography-subtitle1': {
fontSize: '0.875rem',
},
'& .MuiTypography-caption': {
width: 28,
height: 32,
},
'& .MuiPickersSlideTransition-root': {
minWidth: 258,
minHeight: 238,
},
'& [role="row"]': {
margin: '4px 0',
},
'& .MuiDateRangePickerDay-root': {
lineHeight: 0,
margin: 0,
},
'& .MuiPickersArrowSwitcher-root': {
padding: 0,
paddingTop: 0.5,
},
'& .MuiPickersDay-root': {
width: 28,
height: 28,
fontWeight: 'regular',
},
'& .MuiDateRangePickerDay-day.Mui-selected': {
fontWeight: 'semiBold',
},
'& .MuiDateRangePickerDay-day:not(.Mui-selected)': {
borderColor: 'primary.300',
},
'& .MuiPickersLayout-actionBar': {
borderTop: '1px solid',
borderColor: 'divider',
},
},
(theme) =>
theme.applyDarkStyles({
'& > div': {
bgcolor: 'primaryDark.900',
},
'& .MuiDateRangePickerDay-day.Mui-selected': {
color: '#FFF',
},
}),
]}
>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<StaticDateRangePicker
displayStaticWrapperAs="desktop"
defaultValue={[startDate, endDate]}
slots={{
shortcuts: CustomRangeShortcuts,
}}
slotProps={{
shortcuts: {
items: shortcutsItems,
},
}}
/>
</LocalizationProvider>
</Paper>
</Frame.Demo>
<Frame.Info data-mui-color-scheme="dark" sx={{ maxHeight: 300, overflow: 'auto' }}>
<HighlightedCode copyButtonHidden plainStyle code={code} language="jsx" />
</Frame.Info>
</Frame>
);
}

View File

@@ -0,0 +1,282 @@
import * as React from 'react';
import { red, green, yellow, blue } from '@mui/material/colors';
import { alpha } from '@mui/material/styles';
import { DataGridPro, GridPaginationModel } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';
import FormControl from '@mui/material/FormControl';
import FormGroup from '@mui/material/FormGroup';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import Frame from 'docs/src/components/action/Frame';
import XGridGlobalStyles from 'docs/src/components/home/XGridGlobalStyles';
type GridDataType = 'Employee' | 'Commodity';
interface GridPaginationSettings {
pagination: boolean;
autoPageSize: boolean;
paginationModel: GridPaginationModel | undefined;
}
interface GridConfigOptions {
size: number;
type: GridDataType;
pagesize: number;
}
interface GridToolbarContainerProps {
onApply: (options: GridConfigOptions) => void;
size: number;
type: GridDataType;
}
const pageSizeOptions = [25, 100, 1000];
function SettingsPanel(props: GridToolbarContainerProps) {
const { onApply, type, size } = props;
const [sizeState, setSize] = React.useState<number>(size);
const [typeState, setType] = React.useState<GridDataType>(type);
const [selectedPaginationValue, setSelectedPaginationValue] = React.useState<number>(-1);
const handleSizeChange = React.useCallback((event: SelectChangeEvent<string>) => {
setSize(Number(event.target.value));
}, []);
const handleDatasetChange = React.useCallback((event: SelectChangeEvent<GridDataType>) => {
setType(event.target.value as GridDataType);
}, []);
const handlePaginationChange = React.useCallback((event: SelectChangeEvent<number>) => {
setSelectedPaginationValue(Number(event.target.value));
}, []);
const handleApplyChanges = React.useCallback(() => {
onApply({
size: sizeState,
type: typeState,
pagesize: selectedPaginationValue,
});
}, [sizeState, typeState, selectedPaginationValue, onApply]);
return (
<FormGroup
className="MuiFormGroup-options"
sx={{
flexDirection: 'row',
alignContent: { xs: 'start', sm: 'center' },
alignItems: { xs: 'start', sm: 'center' },
'& > *': {
'&:not(:first-child)': { ml: { xs: 0, sm: 1 } },
'&:last-child': { ml: 'auto' },
},
'& .MuiFilledInput-root': {
borderRadius: 1,
backgroundColor: 'transparent',
},
'& .MuiInputBase-sizeSmall': {
fontSize: '0.875rem',
},
}}
>
<FormControl variant="filled" size="small">
<InputLabel id="Dataset">Dataset</InputLabel>
<Select labelId="Dataset" value={typeState} onChange={handleDatasetChange} disableUnderline>
<MenuItem value="Employee">Employee</MenuItem>
<MenuItem value="Commodity">Commodity</MenuItem>
</Select>
</FormControl>
<FormControl variant="filled" size="small">
<InputLabel id="Rows">Rows</InputLabel>
<Select
labelId="Rows"
value={String(sizeState)}
onChange={handleSizeChange}
disableUnderline
>
<MenuItem value={100}>100</MenuItem>
<MenuItem value={1000}>{Number(1000).toLocaleString()}</MenuItem>
<MenuItem value={10000}>{Number(10000).toLocaleString()}</MenuItem>
<MenuItem value={100000}>{Number(100000).toLocaleString()}</MenuItem>
</Select>
</FormControl>
<FormControl variant="filled" size="small" sx={{ minWidth: 80 }}>
<InputLabel id="Page size">Page size</InputLabel>
<Select
labelId="Page size"
value={selectedPaginationValue}
onChange={handlePaginationChange}
disableUnderline
>
<MenuItem value={-1}>off</MenuItem>
<MenuItem value={0}>auto</MenuItem>
{pageSizeOptions.map((pageSize) => (
<MenuItem key={pageSize} value={pageSize}>
{Number(pageSize).toLocaleString()}
</MenuItem>
))}
</Select>
</FormControl>
<Button
variant="outlined"
size="small"
onClick={handleApplyChanges}
sx={{ mt: { xs: 2, sm: 0 }, width: { xs: '100%', sm: 'fit-content' } }}
>
Apply changes
</Button>
</FormGroup>
);
}
export default function XGridFullDemo() {
const [type, setType] = React.useState<GridDataType>('Commodity');
const [size, setSize] = React.useState(100);
const { loading, data, setRowLength, loadNewData } = useDemoData({
dataSet: type,
rowLength: size,
maxColumns: 20,
editable: true,
});
const [pagination, setPagination] = React.useState<GridPaginationSettings>({
pagination: false,
autoPageSize: false,
paginationModel: undefined,
});
const handleApplyClick = (settings: GridConfigOptions) => {
if (size !== settings.size) {
setSize(settings.size);
}
if (type !== settings.type) {
setType(settings.type);
}
if (size !== settings.size || type !== settings.type) {
setRowLength(settings.size);
loadNewData();
}
const newPaginationSettings: GridPaginationSettings = {
pagination: settings.pagesize !== -1,
autoPageSize: settings.pagesize === 0,
paginationModel: settings.pagesize > 0 ? { pageSize: settings.pagesize, page: 0 } : undefined,
};
setPagination(newPaginationSettings);
};
const handlePaginationModelChange = React.useCallback((newModel: GridPaginationModel) => {
setPagination((prev) => ({
...prev,
paginationModel: newModel,
}));
}, []);
return (
<Frame>
<Frame.Demo sx={{ p: 2 }}>
<XGridGlobalStyles selector="#data-grid-full" pro />
<Paper
id="data-grid-full"
variant="outlined"
sx={[
{
borderRadius: '8px',
height: { xs: 320, sm: 500 },
'& .MuiDataGrid-root': {
'& .MuiAvatar-root': { width: 24, height: 24, fontSize: 14, fontWeight: 'bold' },
'& .MuiButton-root': { marginLeft: 0, marginRight: 1 },
'& .MuiChip-root.Rejected': {
color: red[800],
backgroundColor: red[50],
borderColor: red[100],
},
'& .MuiChip-root.Filled': {
color: green[800],
backgroundColor: green[50],
borderColor: green[100],
},
'& .MuiChip-root.Open': {
color: blue[800],
backgroundColor: blue[50],
borderColor: blue[100],
},
'& .MuiChip-root.PartiallyFilled': {
color: 'text.secondary',
backgroundColor: yellow[50],
borderColor: yellow[600],
},
'& .MuiDataGrid-footerContainer': {
minHeight: 48,
borderTop: '1px solid',
borderColor: 'grey.200',
},
'& .MuiTablePagination-root': {
fontSize: '0.75rem',
'& p': {
fontSize: '0.75rem',
},
'& .MuiToolbar-root': {
minHeight: 48,
},
},
},
},
(theme) =>
theme.applyDarkStyles({
'& .MuiDataGrid-root': {
'& .MuiDataGrid-footerContainer': {
borderColor: 'primaryDark.600',
},
'& .MuiChip-root.Rejected': {
color: red[200],
backgroundColor: alpha(red[900], 0.2),
borderColor: alpha(red[700], 0.5),
},
'& .MuiChip-root.Filled': {
color: green[200],
backgroundColor: alpha(green[900], 0.2),
borderColor: alpha(green[700], 0.5),
},
'& .MuiChip-root.Open': {
color: blue[200],
backgroundColor: alpha(blue[900], 0.2),
borderColor: alpha(blue[700], 0.5),
},
'& .MuiChip-root.PartiallyFilled': {
color: yellow[200],
backgroundColor: alpha(yellow[900], 0.2),
borderColor: alpha(yellow[700], 0.2),
},
},
}),
]}
>
<DataGridPro
{...data}
initialState={{ density: 'compact' }}
loading={loading}
checkboxSelection
disableRowSelectionOnClick
pageSizeOptions={pageSizeOptions}
{...pagination}
onPaginationModelChange={handlePaginationModelChange}
showToolbar
/>
</Paper>
</Frame.Demo>
<Frame.Info
data-mui-color-scheme="dark"
sx={{ pl: { xs: 2, sm: 1 }, pr: 2, py: { xs: 2, sm: 1.5 } }}
>
<SettingsPanel onApply={handleApplyClick} size={size} type={type} />
</Frame.Info>
</Frame>
);
}

View File

@@ -0,0 +1,364 @@
import * as React from 'react';
import { red, green, yellow, blue } from '@mui/material/colors';
import Box from '@mui/material/Box';
import Divider from '@mui/material/Divider';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import { StaticDateRangePicker } from '@mui/x-date-pickers-pro/StaticDateRangePicker';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { alpha } from '@mui/material/styles';
import {
DataGridPremium,
useGridApiRef,
useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useDemoData } from '@mui/x-data-grid-generator';
import GradientText from 'docs/src/components/typography/GradientText';
import GetStartedButtons from 'docs/src/components/home/GetStartedButtons';
import HeroContainer from 'docs/src/layouts/HeroContainer';
import IconImage from 'docs/src/components/icon/IconImage';
import FolderTreeView from 'docs/src/components/showcase/FolderTreeView';
import ROUTES from 'docs/src/route';
import dayjs from 'dayjs';
const startDate = dayjs();
startDate.date(10);
const endDate = dayjs();
endDate.date(endDate.date() + 28);
const visibleFields = [
'commodity',
'unitPrice',
'feeRate',
'quantity',
'filledQuantity',
'isFilled',
'traderName',
'status',
'totalPrice',
];
export default function XHero() {
const { loading, data } = useDemoData({
dataSet: 'Commodity',
rowLength: 10000,
editable: true,
visibleFields,
});
const apiRef = useGridApiRef();
const sortedColumns = React.useMemo(() => {
return [...data.columns].sort((a, b) => {
return visibleFields.indexOf(a.field) - visibleFields.indexOf(b.field);
});
}, [data.columns]);
const initialState = useKeepGroupedColumnsHidden({
apiRef,
initialState: {
...data.initialState,
rowGrouping: {
model: ['commodity'],
},
aggregation: {
model: {
quantity: 'sum',
unitPrice: 'avg',
feeRate: 'min',
totalPrice: 'max',
},
},
},
});
const groupingColDef = React.useMemo(
() => ({
headerClassName: 'grouping-column-header',
}),
[],
);
const rowGroupingCounterRef = React.useRef(0);
const isGroupExpandedByDefault = React.useCallback(() => {
rowGroupingCounterRef.current += 1;
return rowGroupingCounterRef.current === 3;
}, []);
return (
<HeroContainer
linearGradient
left={
<Box sx={{ textAlign: { xs: 'center', md: 'left' } }}>
<Typography
variant="body2"
sx={[
{
fontWeight: 'bold',
},
(theme) => ({
color: 'primary.600',
display: 'flex',
alignItems: 'center',
justifyContent: { xs: 'center', md: 'flex-start' },
'& > *': { mr: 1 },
...theme.applyDarkStyles({
color: 'primary.400',
}),
}),
]}
>
<IconImage width={28} height={28} loading="eager" name="product-advanced" /> MUI X
</Typography>
<Typography variant="h1" sx={{ my: 2, maxWidth: 500 }}>
Performant
<br />
<GradientText>advanced</GradientText>
<br /> components
</Typography>
<Typography sx={{ color: 'text.secondary', mb: 3, maxWidth: 500 }}>
Build complex and data-rich applications using a growing list of advanced React
components, like the Data Grid, Date and Time Pickers, Charts, and more!
</Typography>
<GetStartedButtons
primaryUrl={ROUTES.xIntro}
secondaryLabel="Learn about licensing"
secondaryUrl={ROUTES.xLicensing}
/>
</Box>
}
rightSx={{
p: { md: 2, lg: 3, xl: 4 },
overflow: 'hidden', // the components on the Hero section are mostly illustrative, even though they're interactive. That's why scrolling is disabled.
}}
right={
<React.Fragment>
<Paper
variant="outlined"
sx={(theme) => ({
backgroundColor: '#fff',
border: '1px solid',
borderColor: 'divider',
boxShadow: `0px 4px 8px ${alpha(theme.palette.grey[200], 0.6)}`,
mb: { md: 2, lg: 3, xl: 4 },
overflow: 'hidden',
...theme.applyDarkStyles({
borderColor: 'primaryDark.700',
backgroundColor: 'primaryDark.900',
boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.2)',
}),
})}
>
<Typography
variant="body2"
sx={{ fontWeight: 'semiBold', textAlign: 'center', py: 1.5 }}
>
Trades, March 2023
</Typography>
<Divider />
<Box
sx={[
{
height: { md: 300, xl: 370 },
'& .MuiDataGrid-root': {
border: 0,
color: 'text.secondary',
'--DataGrid-rowBorderColor': (theme) => theme.palette.grey[200],
'& .MuiCheckbox-root': {
p: 0.5,
'& > svg': {
fontSize: '1.25rem',
},
},
[`& .MuiDataGrid-aggregationColumnHeaderLabel`]: {
fontWeight: 'normal',
},
[`& .MuiDataGrid-columnHeader:focus, & .MuiDataGrid-columnHeader:focus-within`]:
{
outline: 'none',
},
'& .MuiDataGrid-columnHeaderTitleContainer': {
padding: 0,
color: 'text.primary',
},
'& .MuiDataGrid-columnHeaderTitle': {
flexGrow: 1,
fontSize: '0.875rem',
},
'& button, & button > svg': {
fontSize: 16,
},
'& .MuiChip-root.Rejected': {
color: red[800],
backgroundColor: red[50],
borderColor: red[100],
},
'& .MuiChip-root.Filled': {
color: green[800],
backgroundColor: green[50],
borderColor: green[100],
},
'& .MuiChip-root.Open': {
color: blue[800],
backgroundColor: blue[50],
borderColor: blue[100],
},
'& .MuiChip-root.PartiallyFilled': {
color: 'text.secondary',
backgroundColor: yellow[50],
borderColor: yellow[600],
},
'& .grouping-column-header': {
pl: 6,
},
},
},
(theme) =>
theme.applyDarkStyles({
'& .MuiDataGrid-root': {
'--DataGrid-rowBorderColor': alpha(theme.palette.primaryDark[600], 0.5),
'& .MuiChip-root.Rejected': {
color: red[200],
backgroundColor: alpha(red[900], 0.2),
borderColor: alpha(red[700], 0.5),
},
'& .MuiChip-root.Filled': {
color: green[200],
backgroundColor: alpha(green[900], 0.2),
borderColor: alpha(green[700], 0.5),
},
'& .MuiChip-root.Open': {
color: blue[200],
backgroundColor: alpha(blue[900], 0.2),
borderColor: alpha(blue[700], 0.5),
},
'& .MuiChip-root.PartiallyFilled': {
color: yellow[200],
backgroundColor: alpha(yellow[900], 0.2),
borderColor: alpha(yellow[700], 0.2),
},
'& .MuiDataGrid-pinnedRows': {
backgroundColor: alpha(theme.palette.primaryDark[800], 1),
backgroundImage: 'none',
boxShadow: '0px -6px 12px rgba(0 0 0 / 0.5)',
'& .MuiDataGrid-footerCell': {
color: 'primary.light',
},
},
},
}),
]}
>
<DataGridPremium
{...data}
columns={sortedColumns}
apiRef={apiRef}
initialState={initialState}
disableRowSelectionOnClick
groupingColDef={groupingColDef}
rowHeight={36}
columnHeaderHeight={48}
hideFooter
loading={loading}
isGroupExpandedByDefault={isGroupExpandedByDefault}
/>
</Box>
</Paper>
<Box
sx={{
display: 'flex',
overflow: { md: 'auto', xl: 'unset' },
m: { md: -2, lg: -3, xl: 0 },
p: { md: 2, lg: 3, xl: 0 },
}}
>
<Paper
variant="outlined"
sx={(theme) => ({
minWidth: 300,
mr: { md: 2, lg: 3, xl: 4 },
flexGrow: 1,
backgroundColor: '#fff',
borderColor: 'divider',
boxShadow: `0px 4px 8px ${alpha(theme.palette.grey[200], 0.6)}`,
...theme.applyDarkStyles({
borderColor: 'primaryDark.700',
backgroundColor: 'primaryDark.900',
boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.2)',
}),
})}
>
<Typography variant="body2" sx={{ fontWeight: 'semiBold', p: 2 }}>
Cool UI project
</Typography>
<Divider />
<FolderTreeView />
</Paper>
<Paper
variant="outlined"
sx={[
{
borderColor: 'divider',
boxShadow: (theme) => `0px 4px 12px ${alpha(theme.palette.grey[200], 0.6)}`,
'& > div': {
borderRadius: 1,
overflow: 'auto',
backgroundColor: 'initial',
},
'& .MuiTypography-subtitle1': {
fontSize: '0.875rem',
},
'& .MuiTypography-caption': {
width: { xs: 28, xl: 32 },
height: 32,
},
'& .MuiPickersSlideTransition-root': {
minWidth: { xs: 268, xl: 300 },
minHeight: { xs: 238, xl: 288 },
},
'& [role="row"]': {
margin: { xs: '4px 0', xl: '6px 0' },
},
'& .MuiPickersArrowSwitcher-root': {
padding: 1,
},
'& .MuiDateRangePickerDay-root': {
lineHeight: 0,
margin: 0,
},
'& .MuiPickersDay-root': {
width: { xs: 28, xl: 32 },
height: { xs: 28, xl: 32 },
fontWeight: 400,
},
'& .MuiDateRangePickerDay-day.Mui-selected': {
fontWeight: 600,
},
'& .MuiDateRangePickerDay-day:not(.Mui-selected)': {
borderColor: 'primary.300',
},
},
(theme) =>
theme.applyDarkStyles({
borderColor: 'primaryDark.700',
boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.2)',
backgroundColor: 'primaryDark.900',
'& .MuiDateRangePickerDay-day.Mui-selected': {
color: '#FFF',
},
}),
]}
>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<StaticDateRangePicker
displayStaticWrapperAs="desktop"
defaultValue={[startDate, endDate]}
/>
</LocalizationProvider>
</Paper>
</Box>
</React.Fragment>
}
/>
);
}

Some files were not shown because too many files have changed in this diff Show More