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

[Button] Create ButtonUnstyled and useButton #27600

Merged
merged 32 commits into from
Aug 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8e9e330
Add ButtonUnstyled
michaldudak Jun 16, 2021
4711e17
useButton
michaldudak Jun 16, 2021
c5f12ea
Move props to a separate file
michaldudak Aug 2, 2021
aad757a
Move logic to useButton
michaldudak Aug 3, 2021
8a11765
Make useButton props consistent with ButtonUnstyled
michaldudak Aug 4, 2021
858eca8
Add API docs and demos
michaldudak Aug 4, 2021
11359b7
Fix the build
michaldudak Aug 4, 2021
acbd594
Format demos
michaldudak Aug 5, 2021
7290dee
Add better support for the active state
michaldudak Aug 5, 2021
4f10ac0
Add useButton tests
michaldudak Aug 6, 2021
ee1ae90
Merge remote-tracking branch 'upstream/next' into feat/unstyled-button
michaldudak Aug 6, 2021
f21564a
Write API docs
michaldudak Aug 6, 2021
6b33819
Add more demos
michaldudak Aug 9, 2021
c2ee127
Merge remote-tracking branch 'upstream/next' into feat/unstyled-button
michaldudak Aug 9, 2021
06d4b3c
Use useButton in ButtonBase
michaldudak Aug 12, 2021
68b06ab
Merge remote-tracking branch 'upstream/next' into feat/unstyled-button
michaldudak Aug 12, 2021
08d8f41
Fix tests
michaldudak Aug 12, 2021
b85648c
Use useTouchRipple in ButtonBase
michaldudak Aug 12, 2021
d3be252
Extract chainEventHandlers to utils
michaldudak Aug 12, 2021
fcee0fb
Merge branch 'next' into feat/unstyled-button
michaldudak Aug 12, 2021
126e515
Fix the build
michaldudak Aug 12, 2021
b0f669d
Make demos prettier
michaldudak Aug 12, 2021
065052a
Refactor and correct useButton and demos
michaldudak Aug 13, 2021
4d1fcc9
Do not call all event handlers automatically
michaldudak Aug 13, 2021
a46d7c1
Merge branch 'next' into feat/unstyled-button
michaldudak Aug 15, 2021
52ec0a6
Add dark mode for adv customisation demo
michaldudak Aug 15, 2021
75c5c0b
Fix regression tests
michaldudak Aug 15, 2021
4586840
Apply CR suggestions
michaldudak Aug 17, 2021
ce874d9
Merge remote-tracking branch 'upstream/next' into feat/unstyled-button
michaldudak Aug 20, 2021
df52895
Do not dispatch a click event
michaldudak Aug 20, 2021
41858c1
Remove mergeEventHandlers utility
michaldudak Aug 20, 2021
66019df
Tweak transition speed in demos
michaldudak Aug 20, 2021
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
23 changes: 23 additions & 0 deletions docs/pages/api-docs/button-unstyled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
import jsonPageContent from './button-unstyled.json';

export default function Page(props) {
const { descriptions, pageContent } = props;
return <ApiPage descriptions={descriptions} pageContent={pageContent} />;
}

Page.getInitialProps = () => {
const req = require.context(
'docs/translations/api-docs/button-unstyled',
false,
/button-unstyled.*.json$/,
);
const descriptions = mapApiPageTranslations(req);

return {
descriptions,
pageContent: jsonPageContent,
};
};
25 changes: 25 additions & 0 deletions docs/pages/api-docs/button-unstyled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"props": {
"action": {
"type": {
"name": "union",
"description": "func<br>&#124;&nbsp;{ current?: { focusVisible: func } }"
}
},
"component": { "type": { "name": "elementType" }, "default": "'button'" },
"components": {
"type": { "name": "shape", "description": "{ Root?: elementType }" },
"default": "{}"
},
"disabled": { "type": { "name": "bool" } }
},
"name": "ButtonUnstyled",
"styles": { "classes": [], "globalClasses": {}, "name": null },
"spread": true,
"forwardsRefTo": "HTMLButtonElement",
"filename": "/packages/material-ui-unstyled/src/ButtonUnstyled/ButtonUnstyled.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/buttons/\">Buttons</a></li></ul>",
"styledComponent": true,
"cssComponent": false
}
108 changes: 108 additions & 0 deletions docs/src/pages/components/buttons/UnstyledButtonCustom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import ButtonUnstyled, {
buttonUnstyledClasses,
} from '@material-ui/unstyled/ButtonUnstyled';
import { ThemeProvider, createTheme } from '@material-ui/core/styles';
import { styled, alpha } from '@material-ui/system';

const ButtonRoot = React.forwardRef(function ButtonRoot(props, ref) {
const { children, ...other } = props;

return (
<svg width="150" height="50" {...other} ref={ref}>
<polygon points="0,50 0,0 150,0 150,50" className="bg" />
<polygon points="0,50 0,0 150,0 150,50" className="borderEffect" />
<foreignObject x="0" y="0" width="150" height="50">
<div className="content">{children}</div>
</foreignObject>
</svg>
);
});

ButtonRoot.propTypes = {
children: PropTypes.node,
};

const CustomButtonRoot = styled(ButtonRoot)(
({ theme }) => `
overflow: visible;
cursor: pointer;

& polygon {
fill: transparent;
transition: all 800ms ease;
pointer-events: none;
}

& .bg {
stroke: ${theme.palette.primary.main};
stroke-width: 0.5;
filter: drop-shadow(0 4px 20px rgba(0, 0, 0, 0.1));
}

& .borderEffect {
stroke: ${theme.palette.primary.main};
stroke-width: 2;
stroke-dasharray: 150 600;
stroke-dashoffset: 150;
}

&:hover,
&.${buttonUnstyledClasses.focusVisible} {
.borderEffect {
stroke-dashoffset: -600;
}

.bg {
fill: ${alpha(theme.palette.primary.main, theme.palette.action.hoverOpacity)};
}
}

&:focus,
&.${buttonUnstyledClasses.focusVisible} {
outline: none;
}

&.${buttonUnstyledClasses.active} {
& .bg {
fill: ${alpha(
theme.palette.primary.main,
theme.palette.action.activatedOpacity,
)};
transition: fill 300ms ease-out;
}
}

& foreignObject {
pointer-events: none;

& .content {
font-family: Helvetica, Inter, Arial, sans-serif;
font-size: 14px;
font-weight: 200;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: ${theme.palette.primary.main};
text-transform: uppercase;
}

& svg {
margin: 0 5px;
}
}`,
);

const SvgButton = React.forwardRef(function SvgButton(props, ref) {
return <ButtonUnstyled {...props} component={CustomButtonRoot} ref={ref} />;
});

export default function UnstyledButtonCustom() {
return (
<ThemeProvider theme={createTheme()}>
<SvgButton>Button</SvgButton>
</ThemeProvider>
);
}
110 changes: 110 additions & 0 deletions docs/src/pages/components/buttons/UnstyledButtonCustom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as React from 'react';
import ButtonUnstyled, {
ButtonUnstyledProps,
buttonUnstyledClasses,
} from '@material-ui/unstyled/ButtonUnstyled';
import { Theme, ThemeProvider, createTheme } from '@material-ui/core/styles';
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way we could remove the import from the soon to be named material package? I'm not sure.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. When imported from @material-ui/system, the theme got from createTheme() doesn't have the defaults, so it would be necessary to create all the used tokens. Alternatively, I'm thinking I could rely on just the theme.palette.mode and use hardcoded colors in CSS.

Copy link
Member

Choose a reason for hiding this comment

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

I honestly don't know what would be best. The current tradeoff might already be great.
Another option would be to use the color palette of Joy, if the defaut theme comes fully loaded with it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Or, use the palette from the docs.
However, not relying on theme values would make the demo look the same everywhere. So if someone just takes the demo code and pastes it in their project, they won't be confused that it looks different.
Here's a new implementation - let me know if you like it better (it has not changed visually): michaldudak@40045aa

Copy link
Member

Choose a reason for hiding this comment

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

cc @siriwatknp and @danilo-leal for direction on this.


So if someone just takes the demo code and pastes it in their project, they won't be confused that it looks different.

I had this in mind with the palette color of Joy, say if it comes with blue, red, yellow, purple, orange, etc. by default.

Copy link
Member Author

Choose a reason for hiding this comment

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

Let's move the discussion to #28073

import { styled, alpha } from '@material-ui/system';

const ButtonRoot = React.forwardRef(function ButtonRoot(
props: React.PropsWithChildren<{}>,
ref: React.ForwardedRef<any>,
) {
const { children, ...other } = props;

return (
<svg width="150" height="50" {...other} ref={ref}>
<polygon points="0,50 0,0 150,0 150,50" className="bg" />
<polygon points="0,50 0,0 150,0 150,50" className="borderEffect" />
<foreignObject x="0" y="0" width="150" height="50">
<div className="content">{children}</div>
</foreignObject>
</svg>
);
});

const CustomButtonRoot = styled(ButtonRoot)(
({ theme }: { theme: Theme }) => `
overflow: visible;
cursor: pointer;

& polygon {
fill: transparent;
transition: all 800ms ease;
pointer-events: none;
}

& .bg {
stroke: ${theme.palette.primary.main};
stroke-width: 0.5;
filter: drop-shadow(0 4px 20px rgba(0, 0, 0, 0.1));
}

& .borderEffect {
stroke: ${theme.palette.primary.main};
stroke-width: 2;
stroke-dasharray: 150 600;
stroke-dashoffset: 150;
}

&:hover,
&.${buttonUnstyledClasses.focusVisible} {
.borderEffect {
stroke-dashoffset: -600;
}

.bg {
fill: ${alpha(theme.palette.primary.main, theme.palette.action.hoverOpacity)};
}
}

&:focus,
&.${buttonUnstyledClasses.focusVisible} {
outline: none;
}

&.${buttonUnstyledClasses.active} {
& .bg {
fill: ${alpha(
theme.palette.primary.main,
theme.palette.action.activatedOpacity,
)};
transition: fill 300ms ease-out;
}
}

& foreignObject {
pointer-events: none;

& .content {
font-family: Helvetica, Inter, Arial, sans-serif;
font-size: 14px;
font-weight: 200;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: ${theme.palette.primary.main};
text-transform: uppercase;
}

& svg {
margin: 0 5px;
}
}`,
);

const SvgButton = React.forwardRef(function SvgButton(
props: ButtonUnstyledProps,
ref: React.ForwardedRef<any>,
) {
return <ButtonUnstyled {...props} component={CustomButtonRoot} ref={ref} />;
});

export default function UnstyledButtonCustom() {
return (
<ThemeProvider theme={createTheme()}>
<SvgButton>Button</SvgButton>
</ThemeProvider>
);
}
52 changes: 52 additions & 0 deletions docs/src/pages/components/buttons/UnstyledButtonsSimple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react';
import Stack from '@material-ui/core/Stack';
import ButtonUnstyled, {
buttonUnstyledClasses,
} from '@material-ui/unstyled/ButtonUnstyled';
import { styled } from '@material-ui/system';

const CustomButtonRoot = styled('button')(`
background-color: #007fff;
padding: 15px 20px;
border-radius: 10px;
color: #fff;
font-weight: 600;
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
transition: all 200ms ease;
cursor: pointer;
box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 0 rgba(0, 127, 255, 0);
border: none;

&:hover {
background-color: #0059b2;
}

&.${buttonUnstyledClasses.active} {
background-color: #004386;
}

&.${buttonUnstyledClasses.focusVisible} {
box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 5px rgba(0, 127, 255, 0.5);
outline: none;
}

&.${buttonUnstyledClasses.disabled} {
opacity: 0.5;
cursor: not-allowed;
box-shadow: 0 0 0 0 rgba(0, 127, 255, 0);
}
`);

function CustomButton(props) {
return <ButtonUnstyled {...props} component={CustomButtonRoot} />;
}

export default function UnstyledButton() {
return (
<Stack spacing={2} direction="row">
<CustomButton>Button</CustomButton>
<CustomButton disabled>Disabled</CustomButton>
</Stack>
);
}
53 changes: 53 additions & 0 deletions docs/src/pages/components/buttons/UnstyledButtonsSimple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import Stack from '@material-ui/core/Stack';
import ButtonUnstyled, {
buttonUnstyledClasses,
ButtonUnstyledProps,
} from '@material-ui/unstyled/ButtonUnstyled';
import { styled } from '@material-ui/system';

const CustomButtonRoot = styled('button')(`
background-color: #007fff;
padding: 15px 20px;
border-radius: 10px;
color: #fff;
font-weight: 600;
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
transition: all 200ms ease;
cursor: pointer;
box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 0 rgba(0, 127, 255, 0);
border: none;

&:hover {
background-color: #0059b2;
}

&.${buttonUnstyledClasses.active} {
background-color: #004386;
}

&.${buttonUnstyledClasses.focusVisible} {
box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 5px rgba(0, 127, 255, 0.5);
outline: none;
}

&.${buttonUnstyledClasses.disabled} {
opacity: 0.5;
cursor: not-allowed;
box-shadow: 0 0 0 0 rgba(0, 127, 255, 0);
}
`);

function CustomButton(props: ButtonUnstyledProps) {
return <ButtonUnstyled {...props} component={CustomButtonRoot} />;
}

export default function UnstyledButton() {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export default function UnstyledButton() {
export default function UnstyledButtonsSimple() {

return (
<Stack spacing={2} direction="row">
<CustomButton>Button</CustomButton>
<CustomButton disabled>Disabled</CustomButton>
</Stack>
);
}
Loading