Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Avatar] Use variants api #40324

Merged
merged 11 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/zero-runtime-next-app/src/app/avatar/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import Avatar from '@/components/Avatar/Avatar';
import Stack from '@mui/material/Stack';

export default function Avatars() {
return (
<Stack direction="row" spacing={2}>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</Stack>
);
}
7 changes: 1 addition & 6 deletions apps/zero-runtime-next-app/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@ const Html = styled.html({
color: 'red',
});

const Body = styled.body({
Copy link
Member Author

@mnajdova mnajdova Jan 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaking on other pages would complicate our testing, so I moved it to the home page only.

color: 'rgb(var(--foreground-rgb))',
background: `linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb))`,
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<Html lang="en">
<Body className={inter.className}>{children}</Body>
<body className={inter.className}>{children}</body>
</Html>
);
}
2 changes: 2 additions & 0 deletions apps/zero-runtime-next-app/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { styled } from '@mui/zero-runtime';
import styles from './page.module.css';

const Main = styled.main({
color: 'rgb(var(--foreground-rgb))',
background: `linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb))`,
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
Expand Down
272 changes: 272 additions & 0 deletions apps/zero-runtime-next-app/src/components/Avatar/Avatar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses';
import { styled } from '@mui/zero-runtime';
import { useThemeProps } from '@mui/material/styles';
/* eslint-disable-next-line no-restricted-imports */
import Person from '@mui/material/internal/svg-icons/Person';
import { getAvatarUtilityClass } from '@mui/material/Avatar';

const useUtilityClasses = (ownerState) => {
const { classes, variant, colorDefault } = ownerState;

const slots = {
root: ['root', variant, colorDefault && 'colorDefault'],
img: ['img'],
fallback: ['fallback'],
};

return composeClasses(slots, getAvatarUtilityClass, classes);
};

const AvatarRoot = styled('div', {
name: 'MuiAvatar',
slot: 'Root',
overridesResolver: (props, styles) => {
const { ownerState } = props;

return [
styles.root,
styles[ownerState.variant],
ownerState.colorDefault && styles.colorDefault,
];
},
})(({ theme }) => ({
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
width: 40,
height: 40,
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.pxToRem(20),
lineHeight: 1,
borderRadius: '50%',
overflow: 'hidden',
userSelect: 'none',
variants: [
{
props: { variant: 'rounded' },
style: {
borderRadius: (theme.vars || theme).shape.borderRadius,
},
},
{
props: { variant: 'square' },
style: {
borderRadius: 0,
},
},
{
props: { colorDefault: true },
style: {
color: (theme.vars || theme).palette.background.default,
...(theme.vars
? {
backgroundColor: theme.vars.palette.Avatar.defaultBg,
}
: {
backgroundColor: theme.palette.grey[400],
...theme.applyDarkStyles({ backgroundColor: theme.palette.grey[600] }),
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
}),
},
},
],
}));

const AvatarImg = styled('img', {
name: 'MuiAvatar',
slot: 'Img',
overridesResolver: (props, styles) => styles.img,
})({
width: '100%',
height: '100%',
textAlign: 'center',
// Handle non-square image. The property isn't supported by IE11.
objectFit: 'cover',
// Hide alt text.
color: 'transparent',
// Hide the image broken icon, only works on Chrome.
textIndent: 10000,
});

const AvatarFallback = styled(Person, {
name: 'MuiAvatar',
slot: 'Fallback',
overridesResolver: (props, styles) => styles.fallback,
})({
width: '75%',
height: '75%',
});

function useLoaded({ crossOrigin, referrerPolicy, src, srcSet }) {
const [loaded, setLoaded] = React.useState(false);

React.useEffect(() => {
if (!src && !srcSet) {
return undefined;
}

setLoaded(false);

let active = true;
const image = new Image();
image.onload = () => {
if (!active) {
return;
}
setLoaded('loaded');
};
image.onerror = () => {
if (!active) {
return;
}
setLoaded('error');
};
image.crossOrigin = crossOrigin;
image.referrerPolicy = referrerPolicy;
image.src = src;
if (srcSet) {
image.srcset = srcSet;
}

return () => {
active = false;
};
}, [crossOrigin, referrerPolicy, src, srcSet]);

return loaded;
}

const Avatar = React.forwardRef(function Avatar(inProps, ref) {
const props = useThemeProps({ props: inProps, name: 'MuiAvatar' });
const {
alt,
children: childrenProp,
className,
component = 'div',
imgProps,
sizes,
src,
srcSet,
variant = 'circular',
...other
} = props;

let children = null;

// Use a hook instead of onError on the img element to support server-side rendering.
const loaded = useLoaded({ ...imgProps, src, srcSet });
const hasImg = src || srcSet;
const hasImgNotFailing = hasImg && loaded !== 'error';

const ownerState = {
...props,
colorDefault: !hasImgNotFailing,
component,
variant,
};

const classes = useUtilityClasses(ownerState);

if (hasImgNotFailing) {
children = (
<AvatarImg
alt={alt}
srcSet={srcSet}
src={src}
sizes={sizes}
ownerState={ownerState}
className={classes.img}
{...imgProps}
/>
);
} else if (childrenProp != null) {
children = childrenProp;
} else if (hasImg && alt) {
children = alt[0];
} else {
children = <AvatarFallback ownerState={ownerState} className={classes.fallback} />;
}

return (
<AvatarRoot
as={component}
ownerState={ownerState}
className={clsx(classes.root, className)}
ref={ref}
{...other}
>
{children}
</AvatarRoot>
);
});

Avatar.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the d.ts file and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* Used in combination with `src` or `srcSet` to
* provide an alt attribute for the rendered `img` element.
*/
alt: PropTypes.string,
/**
* Used to render icon or text elements inside the Avatar if `src` is not set.
* This can be an element, or just a string.
*/
children: PropTypes.node,
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* @ignore
*/
className: PropTypes.string,
/**
* The component used for the root node.
* Either a string to use a HTML element or a component.
*/
component: PropTypes.elementType,
/**
* [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes) applied to the `img` element if the component is used to display an image.
* It can be used to listen for the loading error event.
*/
imgProps: PropTypes.object,
/**
* The `sizes` attribute for the `img` element.
*/
sizes: PropTypes.string,
/**
* The `src` attribute for the `img` element.
*/
src: PropTypes.string,
/**
* The `srcSet` attribute for the `img` element.
* Use this attribute for responsive image display.
*/
srcSet: PropTypes.string,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
/**
* The shape of the avatar.
* @default 'circular'
*/
variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
PropTypes.oneOf(['circular', 'rounded', 'square']),
PropTypes.string,
]),
};

export default Avatar;
1 change: 1 addition & 0 deletions packages/mui-joy/src/styles/extendTheme.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('extendTheme', () => {
'unstable_sx',
'shouldSkipGeneratingVar',
'generateCssVars',
'applyDarkStyles',
]).to.includes(field);
});
});
Expand Down
18 changes: 18 additions & 0 deletions packages/mui-joy/src/styles/extendTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
unstable_createGetCssVar as systemCreateGetCssVar,
unstable_styleFunctionSx as styleFunctionSx,
SxConfig,
CSSObject,
} from '@mui/system';
import defaultSxConfig from './sxConfig';
import colors from '../colors';
Expand Down Expand Up @@ -564,6 +565,23 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme {
cssVarPrefix,
getCssVar,
spacing: createSpacing(spacing),
applyDarkStyles(css: CSSObject) {
if ((this as Theme).vars) {
// If CssVarsProvider is used as a provider,
// returns ':where([data-mui-color-scheme="light|dark"]) &'
const selector = (this as Theme)
.getColorSchemeSelector('dark')
.replace(/(\[[^\]]+\])/, ':where($1)');
return {
[selector]: css,
};
}
if ((this as Theme).palette.mode === 'dark') {
return css;
}

return {};
},
} as unknown as Theme; // Need type casting due to module augmentation inside the repo

/**
Expand Down
1 change: 1 addition & 0 deletions packages/mui-joy/src/styles/types/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export interface Theme extends ThemeScales, RuntimeColorSystem {
shouldSkipGeneratingVar: (keys: string[], value: string | number) => boolean;
unstable_sxConfig: SxConfig;
unstable_sx: (props: SxProps) => CSSObject;
applyDarkStyles: (css: CSSObject) => CSSObject;
}

export type SxProps = SystemSxProps<Theme>;
Expand Down
Loading
Loading