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

[experimentalStyled] Add name and slot options #23964

Merged
merged 11 commits into from
Dec 13, 2020
4 changes: 2 additions & 2 deletions packages/material-ui/src/Badge/Badge.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const overridesResolver = (props, styles) => {
const BadgeRoot = styled(
'span',
{},
{ displayName: 'BadgeRoot', className: 'MuiBadge-root', muiName: 'MuiBadge', overridesResolver },
{ name: 'Badge', slot: 'Root', overridesResolver },
Copy link
Member

Choose a reason for hiding this comment

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

I've been meaning to ask – is "slot" a recognised term? I hadn't come across it before your use, and a quick web search doesn't bring up anything relevant.

Copy link
Member Author

@mnajdova mnajdova Dec 12, 2020

Choose a reason for hiding this comment

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

It’s not something standardized, I’ve just got used to use it for describing different parts of the component. In Vue, slots are used as templates part for creating a layout https://vuejs.org/v2/guide/components-slots.html Here is an example:

<div class="container">
  <header>
    <!-- We want header content here -->
    <slot name="header"></slot>
  </header>
  <main>
    <!-- We want main content here -->
    <slot name="body"></slot>
  </main>
  <footer>
    <!-- We want footer content here -->
    <slot name="footer"></slot>
  </footer>
</div>

I don’t think react has a similar notion for it, but I think we can just use the same term. Open for other options around naming here :)

Copy link
Member

Choose a reason for hiding this comment

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

I was influenced by the usage of the slot terminology by Vue and Web Components when making the initial proposal. The first idea that came to mind was component but it seemed to be overloaded. Hence slot as the second option.

Any other idea?

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

@mnajdova mnajdova Dec 13, 2020

Choose a reason for hiding this comment

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

part is the other term I had in mind too, but slot sound better to me 🤷‍♀️

Copy link
Member

Choose a reason for hiding this comment

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

svelte has slots as well.

Copy link
Member

Choose a reason for hiding this comment

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

I'm struggling to see how "slot" as used here relates to slots in Vue and Svelte, where it appears to be a placeholder for children equivalent to {props.children} in React. A slot is something that you put things into. That doesn't seem to be the case here, unless I'm misunderstanding.

Copy link
Member

Choose a reason for hiding this comment

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

@mbrookes each components' prop key is a slot.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, thanks.

)({
position: 'relative',
display: 'inline-flex',
Expand All @@ -68,7 +68,7 @@ const BadgeRoot = styled(
const BadgeBadge = styled(
'span',
{},
{ displayName: 'BadgeBadge', className: 'MuiBadge-badge', overridesResolver },
{ name: 'Badge', slot: 'Badge', overridesResolver },
)((props) => ({
display: 'flex',
flexDirection: 'row',
Expand Down
17 changes: 8 additions & 9 deletions packages/material-ui/src/Slider/Slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,8 @@ export const SliderRoot = experimentalStyled(
'span',
{},
{
displayName: 'SliderRoot',
muiName: 'MuiSlider',
className: 'MuiSlider-root',
name: 'Slider',
slot: 'Root',
overridesResolver,
},
)((props) => ({
Expand Down Expand Up @@ -136,7 +135,7 @@ export const SliderRoot = experimentalStyled(
export const SliderRail = experimentalStyled(
'span',
{},
{ displayName: 'SliderRail', className: 'MuiSlider-rail' },
{ name: 'Slider', slot: 'Rail' },
)((props) => ({
display: 'block',
position: 'absolute',
Expand All @@ -157,7 +156,7 @@ export const SliderRail = experimentalStyled(
export const SliderTrack = experimentalStyled(
'span',
{},
{ displayName: 'SliderTrack', className: 'MuiSlider-track' },
{ name: 'Slider', slot: 'Track' },
)((props) => ({
display: 'block',
position: 'absolute',
Expand All @@ -182,7 +181,7 @@ export const SliderTrack = experimentalStyled(
export const SliderThumb = experimentalStyled(
'span',
{},
{ displayName: 'SliderThumb', className: 'MuiSlider-thumb' },
{ name: 'Slider', slot: 'Thumb' },
)((props) => ({
position: 'absolute',
width: 12,
Expand Down Expand Up @@ -248,7 +247,7 @@ export const SliderThumb = experimentalStyled(
export const SliderValueLabel = experimentalStyled(
SliderValueLabelUnstyled,
{},
{ displayName: 'SliderValueLabel', className: 'MuiSlider-valueLabel' },
{ name: 'Slider', slot: 'ValueLabel' },
)((props) => ({
// IE 11 centering bug, to remove from the customization demos once no longer supported
left: 'calc(-50% - 4px)',
Expand All @@ -271,7 +270,7 @@ export const SliderValueLabel = experimentalStyled(
export const SliderMark = experimentalStyled(
'span',
{},
{ displayName: 'SliderMark', className: 'MuiSlider-mark' },
{ name: 'Slider', slot: 'Mark' },
)((props) => ({
position: 'absolute',
width: 2,
Expand All @@ -287,7 +286,7 @@ export const SliderMark = experimentalStyled(
export const SliderMarkLabel = experimentalStyled(
'span',
{},
{ displayName: 'SliderMarkLabel', className: 'MuiSlider-markLabel' },
{ name: 'Slider', slot: 'MarkLabel' },
)((props) => ({
...props.theme.typography.body2,
color: props.theme.palette.text.secondary,
Expand Down
5 changes: 2 additions & 3 deletions packages/material-ui/src/styles/experimentalStyled.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,8 @@ export interface StyledOptions {
}

interface MuiStyledOptions<Theme extends object = any> {
displayName?: string;
className?: string;
muiName?: string;
name: string;
slot: string;
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
overridesResolver?: (props: any, styles: string | object, name: string) => string | object;
skipSx?: boolean;
}
Expand Down
25 changes: 20 additions & 5 deletions packages/material-ui/src/styles/experimentalStyled.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,26 @@ const variantsResolver = (props, styles, theme, name) => {
const shouldForwardProp = (prop) =>
prop !== 'styleProps' && prop !== 'theme' && prop !== 'isRtl' && prop !== 'sx' && prop !== 'as';

const lowercaseFirstLetter = (string) => {
return string.charAt(0).toLowerCase() + string.slice(1);
};

const experimentalStyled = (tag, options, muiOptions = {}) => {
const displayName = muiOptions.displayName;
const name = muiOptions.muiName;
const className = muiOptions.className;
const componentName = muiOptions.name;
const componentSlot = muiOptions.slot;
const skipSx = muiOptions.skipSx || false;

let displayName, name, className;

if (componentName && componentSlot) {
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
displayName = `${componentName}${componentSlot}`;
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
name = componentSlot === 'Root' ? `Mui${componentName}` : null;
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
className = `Mui${componentName}-${lowercaseFirstLetter(componentSlot)}`;
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
}

const defaultStyledResolver = styled(tag, {
shouldForwardProp,
label: className || name || displayName,
label: className || componentName || '',
...options,
});
const muiStyledResolver = (styleArg, ...expressions) => {
Expand Down Expand Up @@ -123,7 +135,10 @@ const experimentalStyled = (tag, options, muiOptions = {}) => {
}

const Component = defaultStyledResolver(transformedStyleArg, ...expressionsWithDefaultTheme);
Component.displayName = displayName || name;

if (displayName || name) {
Component.displayName = displayName || name;
}

return Component;
};
Expand Down
53 changes: 17 additions & 36 deletions packages/material-ui/src/styles/experimentalStyled.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ describe('experimentalStyled', () => {
Test = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ muiName: 'MuiTest', overridesResolver: testOverridesResolver },
{ name: 'Test', slot: 'Root', overridesResolver: testOverridesResolver },
)`
width: 200px;
height: 300px;
Expand All @@ -185,7 +185,7 @@ describe('experimentalStyled', () => {
TestObj = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ muiName: 'MuiTest', overridesResolver: testOverridesResolver },
{ name: 'Test', slot: 'Root', overridesResolver: testOverridesResolver },
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
)({
width: '200px',
height: '300px',
Expand Down Expand Up @@ -363,7 +363,7 @@ describe('experimentalStyled', () => {
const TestNoSx = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ muiName: 'MuiTest', overridesResolver: testOverridesResolver, skipSx: true },
{ name: 'Test', slot: 'Root', overridesResolver: testOverridesResolver, skipSx: true },
)(({ sx = {} }) => ({
...(sx.mt && {
marginTop: `${sx.mt * -1}px`,
Expand All @@ -384,7 +384,7 @@ describe('experimentalStyled', () => {
const TestWithSx = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ muiName: 'MuiTest', overridesResolver: testOverridesResolver },
{ name: 'Test', slot: 'Root', overridesResolver: testOverridesResolver },
)(({ sx = {} }) => ({
...(sx.mt && {
marginTop: `${sx.m * -1}px`,
Expand All @@ -407,75 +407,56 @@ describe('experimentalStyled', () => {
const Component = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ displayName: 'Component', muiName: 'MuiComponent' },
{ name: 'Component' },
)`
width: 200px;
height: 300px;
`;

expect(Component.displayName).to.equal("Component");
expect(Component.displayName).to.equal('Component');
});

it('should set displayName as muiName if displayName is not specified', () => {
it('should set displayName as name + slot if both are specified', () => {
const Component = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ muiName: 'MuiComponent' },
{ name: 'Component', slot: 'Root' },
)`
width: 200px;
height: 300px;
`;

expect(Component.displayName).to.equal("MuiComponent");
expect(Component.displayName).to.equal('ComponentRoot');
});

it('should use className when generating the classes', () => {
it('should set the className when generating the classes', () => {
const Component = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ displayName: 'Component', muiName: 'MuiComponent', className: 'MuiComponent-root' },
{ name: 'Component', slot: 'Slot' },
)`
width: 200px;
height: 300px;
`;

const { container } = render(
<Component>Test</Component>,
);

expect(container.firstChild.getAttribute('class').includes('MuiComponent-root')).to.equal(true);
});

it('should use muiName as className when generating the classes if className is not defined', () => {
const Component = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ displayName: 'Component', muiName: 'MuiComponent' },
)`
width: 200px;
height: 300px;
`;
const { container } = render(<Component>Test</Component>);

const { container } = render(
<Component>Test</Component>,
expect(container.firstChild.getAttribute('class').includes('MuiComponent-slot')).to.equal(
true,
);

expect(container.firstChild.getAttribute('class').includes('MuiComponent')).to.equal(true);
});

it('should use displayName as className when generating the classes if className and muiName are not defined', () => {
it('should use name as className when slot is not specified', () => {
const Component = styled(
'div',
{ shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' && prop !== 'sx' },
{ displayName: 'Component' },
{ name: 'Component' },
)`
width: 200px;
height: 300px;
`;

const { container } = render(
<Component>Test</Component>,
);
const { container } = render(<Component>Test</Component>);

expect(container.firstChild.getAttribute('class').includes('Component')).to.equal(true);
});
Expand Down