Commit fce14d5b authored by Mickaël Bourgier's avatar Mickaël Bourgier
Browse files

Move CSS "logic" of Button into a new Box component

parent 40a08772
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { withStyles } from '../styles';
import styles from './Box.styles';
/**
* @summary Boîte stylisée en fonction des couleurs du thème
*/
class Box extends Component {
render() {
const {
classes,
className: classNameProps,
color,
component: Component = 'div',
disabled,
htmlDisabled,
interactive = false,
muted,
shadow = false,
shape = 'square',
theme,
transitions = true,
variant = 'plain',
...otherProps
} = this.props;
return (
<Component
className={classNames(
classes.root,
classes[`${color}Color`],
classes[`${variant}Variant`],
classes[`${shape}Shape`],
{
[classes.shadow]: shadow,
[classes.muted]: muted,
[classes.disabled]: disabled,
[classes.interactive]: interactive,
[classes.transitions]: interactive && transitions
},
classNameProps
)}
disabled={htmlDisabled}
{...otherProps}
/>
);
}
}
Box.propTypes = {
/**
* Contenu du composant
*/
children: PropTypes.node,
/**
* Objet permettant de modifier les classes utilisées par le composant
*/
classes: PropTypes.object,
/**
* Nom(s) de classe(s) CSS à inclure dans le composant racine
*/
className: PropTypes.string,
/**
* Couleur de la boîte
*/
color: PropTypes.oneOfType([
PropTypes.oneOf([
'primary',
'secondary',
'success',
'danger',
'warning',
'info',
'light',
'dark'
]),
PropTypes.string
]),
/**
* Composant à utiliser en tant que composant racine
*/
component: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.object
]),
/**
* Désactive la boîte : elle perd sa couleur et devient grise
*/
disabled: PropTypes.bool,
/**
* Permet de fournir la prop `disabled` à l'élément sous-jacent
*/
htmlDisabled: PropTypes.bool,
/**
* Active les effets sur les pseudo-classe `:hover`, `:focus` et `:active`
*/
interactive: PropTypes.bool,
/**
* Rend la boîte muette : elle garde sa couleur mais devient peu visible
*/
muted: PropTypes.bool,
/**
* Ajoute une ombre
*/
shadow: PropTypes.bool,
/**
* Forme de la boîte
*/
shape: PropTypes.oneOf(['circle', 'rounded', 'square']),
/** @ignore */
theme: PropTypes.object,
/**
* Active ou non les transitions sur les propriétés qui changent avec les
* intéractions de l'utilisateur (uniquement utilisée en combinaison avec la
* prop `interactive`)
*/
transitions: PropTypes.bool,
/**
* Variante de la boîte
*/
variant: PropTypes.oneOf(['text', 'outline', 'plain', 'gradient'])
};
export default withStyles(styles, { withTheme: true })(Box);
import Color from 'color';
export default theme => ({
root: {
backgroundColor: 'transparent',
backgroundImage: 'none',
borderColor: 'transparent',
borderStyle: 'solid',
borderWidth: 1,
boxShadow: 'none',
color: 'inherit'
},
plainVariant: {},
textVariant: {},
outlineVariant: {},
gradientVariant: {},
...Object.assign(
...Object.keys(theme.colors.palette).map(color => ({
[`${color}Color`]: {
'&$textVariant': {
color: theme.colors.palette[color],
'&$interactive:not($muted)': {
'&:hover, &:focus': {
backgroundColor: Color(theme.colors.palette[color])
.fade(0.9)
.string(),
color: theme.colors.hover[color]
},
'&:active': {
backgroundColor: Color(theme.colors.palette[color])
.fade(0.8)
.string(),
color: theme.colors.active[color]
}
}
},
'&$outlineVariant': {
color: theme.colors.palette[color],
borderColor: theme.colors.palette[color],
'&$interactive:not($muted)': {
'&:hover, &:focus': {
backgroundColor: Color(theme.colors.palette[color])
.fade(0.9)
.string(),
borderColor: theme.colors.hover[color],
color: theme.colors.hover[color]
},
'&:active': {
backgroundColor: Color(theme.colors.palette[color])
.fade(0.8)
.string(),
borderColor: theme.colors.active[color],
color: theme.colors.active[color]
}
}
},
'&$plainVariant': {
backgroundColor: theme.colors.palette[color],
color: theme.colors.contrastText[color],
'&$interactive:not($muted)': {
'&:hover, &:focus': {
backgroundColor: theme.colors.hover[color]
},
'&:active': {
backgroundColor: theme.colors.active[color]
}
}
},
'&$gradientVariant': {
backgroundColor: theme.colors.palette[color],
backgroundImage: theme.colors.gradient[color],
backgroundPosition: '100% 0',
backgroundSize: '200%',
borderWidth: 0,
color: theme.colors.contrastText[color],
'&$interactive:not($muted)': {
'&:hover, &:focus': {
backgroundPosition: '50% 0'
},
'&:active': {
backgroundPosition: '0 0'
}
},
'&$shadow': {
boxShadow: `0 0.4rem 2rem 0 ${Color(theme.colors.palette[color])
.fade(0.8)
.string()}, 0 0.7rem 1rem -0.5rem ${Color(
theme.colors.palette[color]
)
.fade(0.4)
.string()}`,
'&$interactive': {
'&:hover, &:focus, &:active': {
boxShadow: `0 0.4rem 3rem 0 ${Color(theme.colors.palette[color])
.fade(0.8)
.string()}, 0 0.7rem 2rem -0.5rem ${Color(
theme.colors.palette[color]
)
.fade(0.4)
.string()}`
}
}
}
}
}
}))
),
muted: {
boxShadow: 'none !important',
opacity: 0.5
},
disabled: {
boxShadow: 'none !important',
color: `${theme.colors.disabled} !important`,
opacity: 1,
'&$textVariant, &$outlineVariant': {
backgroundColor: 'transparent !important'
},
'&$outlineVariant': {
borderColor: `${Color(theme.colors.disabled)
.fade(0.8)
.string()} !important`
},
'&$plainVariant, &$gradientVariant': {
backgroundColor: `${Color(theme.colors.disabled)
.fade(0.8)
.string()} !important`
},
'&$gradientVariant': {
backgroundImage: 'none',
borderWidth: 1
}
},
shadow: {
'&:not($textVariant):not($gradientVariant):not($muted):not($disabled)': {
boxShadow: theme.shadows[2],
'&$interactive': {
'&:hover, &:focus, &:active': {
boxShadow: theme.shadows[3]
}
}
}
},
squareShape: {
borderRadius: 0
},
roundedShape: {
borderRadius: theme.spacing.spacers[1]
},
circleShape: {
borderRadius: '100rem'
},
interactive: {},
transitions: {
transition: theme.transitions.create(
[
'background-position',
'background-color',
'border-color',
'box-shadow',
'color',
'opacity'
],
{
duration: theme.transitions.durations.short,
easing: theme.transitions.easings.easeInOut
}
)
}
});
import React, { Fragment, PureComponent } from 'react';
import { createCachedFunction, ucfirst } from '@webalt/utils';
import { Box, Col, Row, withStyles } from '@webalt/react';
const styles = theme => ({
box: {
height: 50,
width: 50
},
disabled: {
color: theme.colors.disabled
}
});
class Variants extends PureComponent {
state = {
disabled: false,
interactive: false,
muted: false,
selectedVariant: 'plain',
shadow: false,
selectedShape: 'square',
transitions: true
};
handleRadioChange = createCachedFunction((name, event) => {
this.setState({
[name]: event.target.value
});
});
handleBooleanPropChange = createCachedFunction((name, event) => {
this.setState({
[name]: event.target.checked
});
});
render() {
const { classes, theme } = this.props;
const {
disabled,
interactive,
muted,
selectedShape,
selectedVariant,
shadow,
transitions
} = this.state;
return (
<Fragment>
<Row justifyContent='center'>
{['text', 'outline', 'plain', 'gradient'].map(variant => (
<Col key={variant} xs={4} sm={3} lg='auto'>
<input
id={`${variant}Variant`}
checked={selectedVariant === variant}
onChange={this.handleRadioChange('selectedVariant')}
type='radio'
value={variant}
/>
<label htmlFor={`${variant}Variant`}>
&nbsp;{ucfirst(variant)}
</label>
</Col>
))}
</Row>
<Row justifyContent='center'>
{['shadow', 'muted', 'disabled', 'interactive', 'transitions'].map(
prop => (
<Col key={prop} xs={4} sm={3} lg='auto'>
<input
checked={this.state[prop]}
disabled={
(prop === 'shadow' && selectedVariant === 'text') ||
(prop === 'muted' && disabled) ||
(prop === 'transitions' && !interactive)
}
id={`${prop}Prop`}
onChange={this.handleBooleanPropChange(prop)}
type='checkbox'
/>
<label
className={
(prop === 'shadow' && selectedVariant === 'text') ||
(prop === 'muted' && disabled) ||
(prop === 'transitions' && !interactive)
? classes.disabled
: null
}
htmlFor={`${prop}Prop`}
>
&nbsp;{ucfirst(prop)}
</label>
</Col>
)
)}
</Row>
<Row justifyContent='center'>
{['circle', 'rounded', 'square'].map(shape => (
<Col key={shape} xs={4} sm={3} lg='auto'>
<input
id={`${shape}Prop`}
checked={selectedShape === shape}
onChange={this.handleRadioChange('selectedShape')}
type='radio'
value={shape}
/>
<label htmlFor={`${shape}Prop`}>&nbsp;{ucfirst(shape)}</label>
</Col>
))}
</Row>
<Row className='mt-2'>
{Object.keys(theme.colors.palette).map(color => (
<Col key={color} style={{ textAlign: 'center' }}>
<Box
color={color}
className={classes.box}
disabled={disabled}
interactive={interactive}
muted={muted}
shadow={shadow}
shape={selectedShape}
variant={selectedVariant}
transitions={transitions}
>
Box
</Box>
</Col>
))}
</Row>
</Fragment>
);
}
}
export default withStyles(styles, { withTheme: true })(Variants);
export { default } from './Box';
......@@ -5,6 +5,7 @@ import classNames from 'classnames';
import { withStyles } from '../styles';
import multiplyUnit from '../utils/multiplyUnit';
import Box from '../Box';
import styles from './Button.styles';
/**
......@@ -23,16 +24,15 @@ class Button extends Component {
const {
children,
classes,
className: classNameProps,
className: classNameProp,
color = 'primary',
component: Component = 'button',
component = 'button',
disabled,
fullWidth = false,
iconPadding = 2,
muted,
onClick,
pill = false,
shadow = false,
size = 'medium',
square = false,
theme,
......@@ -42,20 +42,14 @@ class Button extends Component {
const className = classNames(
classes.root,
size !== 'medium' && classes[`${size}Size`],
classes[`${color}Color`],
classes[`${variant}Variant`],
size !== 'medium' && classes[`${size}Size`],
{
[classes.shadow]: shadow,
[classes.square]: square,
[classes.pill]: pill,
[classes.disabled]: disabled,
[classes.fullWidth]: fullWidth,
[classes.muted]: muted,
[classes.disabled]: disabled
[classes.muted]: muted
},
classNameProps
classNameProp
);
const iconSizeByButtonSize = {
......@@ -65,10 +59,17 @@ class Button extends Component {
};
return (
<Component
<Box
className={className}
disabled={Component === 'button' ? disabled || muted : undefined}
color={color}
component={component}
disabled={disabled}
htmlDisabled={component === 'button' ? disabled || muted : undefined}
interactive
muted={muted}
onClick={onClick ? this.handleClick : undefined}
shape={pill ? 'circle' : square ? 'square' : 'rounded'}
variant={variant}
{...otherProps}
>
{React.Children.map(children, child => {
......@@ -89,7 +90,7 @@ class Button extends Component {
: {}
);
})}
</Component>
</Box>
);
}
}
......
import Color from 'color';
import multiplyUnit from '../utils/multiplyUnit';
export default theme => ({
root: {
alignItems: 'center',
backgroundColor: 'transparent',
backgroundImage: 'none',
borderColor: 'transparent',
borderRadius: theme.spacing.spacers[1],
borderStyle: 'solid',
borderWidth: 1,
boxShadow: 'none',
color: 'inherit',
cursor: 'pointer',
display: 'inline-flex',
fontFamily: 'inherit',
......@@ -21,34 +12,10 @@ export default theme => ({
outline: '0 !important',
padding: `${theme.spacing.spacers[1]} ${theme.spacing.spacers[3]}`,
textDecoration: 'none !important',
transition: theme.transitions.create(
[
'background-position',
'background-color',
'border-color',
'box-shadow',
'color',
'opacity'
],
{
duration: theme.transitions.durations.short,
easing: theme.transitions.easings.easeInOut
}
),
userSelect: 'none',
verticalAlign: 'middle'
},
plainVariant: {},
textVariant: {
backgroundColor: 'transparent'
},
outlineVariant: {
backgroundColor: 'transparent'
},