Skip to content

Commit

Permalink
Merge pull request #4071 from marmelab/button-types
Browse files Browse the repository at this point in the history
Add types for button components
  • Loading branch information
djhi authored Nov 28, 2019
2 parents f2b6e12 + ddbeca3 commit 5edd79b
Show file tree
Hide file tree
Showing 24 changed files with 611 additions and 421 deletions.
2 changes: 1 addition & 1 deletion packages/ra-core/src/dataProvider/useMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export interface MutationOptions {
}

export type UseMutationValue = [
(query: Partial<Mutation>, options?: Partial<MutationOptions>) => void,
(query?: Partial<Mutation>, options?: Partial<MutationOptions>) => void,
{
data?: any;
total?: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-core/src/sideEffect/redirection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type RedirectToFunction = (
data: any
) => string;

export type RedirectionSideEffect = string | false | RedirectToFunction;
export type RedirectionSideEffect = string | boolean | RedirectToFunction;

interface ActionWithSideEffect {
type: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-core/src/sideEffect/useRedirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type RedirectToFunction = (
data?: Record
) => string;

export type RedirectionSideEffect = string | false | RedirectToFunction;
export type RedirectionSideEffect = string | boolean | RedirectToFunction;

/**
* Hook for Redirection Side Effect
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,32 @@
import React from 'react';
import React, { FC, ReactElement } from 'react';
import PropTypes from 'prop-types';
import {
Button as MuiButton,
Tooltip,
IconButton,
useMediaQuery,
makeStyles,
PropTypes as MuiPropTypes,
} from '@material-ui/core';
import { ButtonProps as MuiButtonProps } from '@material-ui/core/Button';
import { Theme } from '@material-ui/core';
import classnames from 'classnames';
import { useTranslate } from 'ra-core';

const useStyles = makeStyles(
{
button: {
display: 'inline-flex',
alignItems: 'center',
},
label: {
paddingLeft: '0.5em',
},
labelRightIcon: {
paddingRight: '0.5em',
},
smallIcon: {
fontSize: 20,
},
mediumIcon: {
fontSize: 22,
},
largeIcon: {
fontSize: 24,
},
},
{ name: 'RaButton' }
);

const Button = ({
/**
* A generic Button with side icon. Only the icon is displayed on small screens.
*
* The component translates the label. Pass the icon as child.
* The icon displays on the left side of the button by default. Set alignIcon prop to 'right' to inverse.
*
* @example
*
* <Button label="Edit" color="secondary" onClick={doEdit}>
* <ContentCreate />
* </Button>
*
*/
const Button: FC<ButtonProps> = ({
alignIcon = 'left',
children,
classes: classesOverride,
Expand All @@ -48,7 +39,9 @@ const Button = ({
}) => {
const translate = useTranslate();
const classes = useStyles({ classes: classesOverride });
const isXSmall = useMediaQuery(theme => theme.breakpoints.down('xs'));
const isXSmall = useMediaQuery((theme: Theme) =>
theme.breakpoints.down('xs')
);

return isXSmall ? (
label && !disabled ? (
Expand Down Expand Up @@ -105,12 +98,50 @@ const Button = ({
);
};

const useStyles = makeStyles(
{
button: {
display: 'inline-flex',
alignItems: 'center',
},
label: {
paddingLeft: '0.5em',
},
labelRightIcon: {
paddingRight: '0.5em',
},
smallIcon: {
fontSize: 20,
},
mediumIcon: {
fontSize: 22,
},
largeIcon: {
fontSize: 24,
},
},
{ name: 'RaButton' }
);

interface Props {
alignIcon?: 'left' | 'right';
children?: ReactElement;
classes?: object;
className?: string;
color?: MuiPropTypes.Color;
disabled?: boolean;
label?: string;
size?: 'small' | 'medium' | 'large';
}

export type ButtonProps = Props & MuiButtonProps;

Button.propTypes = {
alignIcon: PropTypes.string,
alignIcon: PropTypes.oneOf(['left', 'right']),
children: PropTypes.element,
classes: PropTypes.object,
className: PropTypes.string,
color: PropTypes.string,
color: PropTypes.oneOf(['default', 'inherit', 'primary', 'secondary']),
disabled: PropTypes.bool,
label: PropTypes.string,
size: PropTypes.oneOf(['small', 'medium', 'large']),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import expect from 'expect';
import { shallow } from 'enzyme';
import React from 'react';

Expand All @@ -6,7 +7,7 @@ import { CloneButton } from './CloneButton';
describe('<CloneButton />', () => {
it('should pass a clone of the record in the location state', () => {
const wrapper = shallow(
<CloneButton record={{ id: 123, foo: 'bar' }} />
<CloneButton record={{ id: 123, foo: 'bar' }} basePath="" />
);

expect(wrapper.prop('to')).toEqual(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
import React from 'react';
import React, { FC, ReactElement } from 'react';
import PropTypes from 'prop-types';
import shouldUpdate from 'recompose/shouldUpdate';
import Queue from '@material-ui/icons/Queue';
import { Link } from 'react-router-dom';
import { stringify } from 'query-string';
import { Record } from 'ra-core';

import Button from './Button';
import Button, { ButtonProps } from './Button';

export const CloneButton: FC<CloneButtonProps> = ({
basePath = '',
label = 'ra.action.clone',
record,
icon = defaultIcon,
...rest
}) => (
<Button
component={Link}
to={
record
? {
pathname: `${basePath}/create`,
search: stringify({
source: JSON.stringify(omitId(record)),
}),
}
: `${basePath}/create`
}
label={label}
onClick={stopPropagation}
{...sanitizeRestProps(rest)}
>
{icon}
</Button>
);

const defaultIcon = <Queue />;

// useful to prevent click bubbling in a datagrid with rowClick
const stopPropagation = e => e.stopPropagation();

const omitId = ({ id, ...rest }) => rest;
const omitId = ({ id, ...rest }: Record) => rest;

const sanitizeRestProps = ({
// the next 6 props are injected by Toolbar
Expand All @@ -21,41 +51,25 @@ const sanitizeRestProps = ({
saving,
submitOnEnter,
...rest
}) => rest;
}: any) => rest;

export const CloneButton = ({
basePath = '',
label = 'ra.action.clone',
record = {},
icon = <Queue />,
...rest
}) => (
<Button
component={Link}
to={{
pathname: `${basePath}/create`,
search: stringify({ source: JSON.stringify(omitId(record)) }),
}}
label={label}
onClick={stopPropagation}
{...sanitizeRestProps(rest)}
>
{icon}
</Button>
);
interface Props {
basePath?: string;
record?: Record;
icon?: ReactElement;
}

export type CloneButtonProps = Props & ButtonProps;

CloneButton.propTypes = {
basePath: PropTypes.string,
className: PropTypes.string,
classes: PropTypes.object,
label: PropTypes.string,
record: PropTypes.object,
icon: PropTypes.element,
label: PropTypes.string,
record: PropTypes.any,
};

const enhance = shouldUpdate(
(props, nextProps) =>
props.translate !== nextProps.translate ||
(props: Props, nextProps: Props) =>
(props.record &&
nextProps.record &&
props.record !== nextProps.record) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,35 @@
import React from 'react';
import React, { FC, ReactElement } from 'react';
import PropTypes from 'prop-types';
import onlyUpdateForKeys from 'recompose/onlyUpdateForKeys';
import { Fab, makeStyles, useMediaQuery } from '@material-ui/core';
import { Fab, makeStyles, useMediaQuery, Theme } from '@material-ui/core';
import ContentAdd from '@material-ui/icons/Add';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { useTranslate } from 'ra-core';

import Button from './Button';
import Button, { ButtonProps } from './Button';

const useStyles = makeStyles(
theme => ({
floating: {
color: theme.palette.getContrastText(theme.palette.primary.main),
margin: 0,
top: 'auto',
right: 20,
bottom: 60,
left: 'auto',
position: 'fixed',
zIndex: 1000,
},
floatingLink: {
color: 'inherit',
},
}),
{ name: 'RaCreateButton' }
);

const CreateButton = ({
const CreateButton: FC<CreateButtonProps> = ({
basePath = '',
className,
classes: classesOverride,
label = 'ra.action.create',
icon = <ContentAdd />,
icon = defaultIcon,
...rest
}) => {
const classes = useStyles({ classes: classesOverride });
const translate = useTranslate();
const isSmall = useMediaQuery(theme => theme.breakpoints.down('sm'));
const isSmall = useMediaQuery((theme: Theme) =>
theme.breakpoints.down('sm')
);
return isSmall ? (
<Fab
component={Link}
color="primary"
className={classnames(classes.floating, className)}
to={`${basePath}/create`}
aria-label={label && translate(label)}
{...rest}
{...rest as any}
>
{icon}
</Fab>
Expand All @@ -56,20 +39,47 @@ const CreateButton = ({
to={`${basePath}/create`}
className={className}
label={label}
{...rest}
{...rest as any}
>
{icon}
</Button>
);
};

const defaultIcon = <ContentAdd />;

const useStyles = makeStyles(
theme => ({
floating: {
color: theme.palette.getContrastText(theme.palette.primary.main),
margin: 0,
top: 'auto',
right: 20,
bottom: 60,
left: 'auto',
position: 'fixed',
zIndex: 1000,
},
floatingLink: {
color: 'inherit',
},
}),
{ name: 'RaCreateButton' }
);

interface Props {
basePath?: string;
icon?: ReactElement;
}

export type CreateButtonProps = Props & ButtonProps;

CreateButton.propTypes = {
basePath: PropTypes.string,
className: PropTypes.string,
classes: PropTypes.object,
label: PropTypes.string,
size: PropTypes.string,
className: PropTypes.string,
icon: PropTypes.element,
label: PropTypes.string,
};

const enhance = onlyUpdateForKeys(['basePath', 'label', 'translate']);
Expand Down
Loading

0 comments on commit 5edd79b

Please sign in to comment.