Skip to content

Commit

Permalink
update: Migrate some class components to function components. (#319)
Browse files Browse the repository at this point in the history
* Convert things.

* Fix some tests.

* Fix tests.

* Fix copy.

* Fix tests.
  • Loading branch information
milesj authored and Miles Johnson committed Mar 4, 2020
1 parent 988ba64 commit 25a969a
Show file tree
Hide file tree
Showing 28 changed files with 491 additions and 685 deletions.
1 change: 0 additions & 1 deletion configs/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ module.exports = {

// Disabled until we migrate to hooks
'react/no-did-update-set-state': 'off',
'react/prefer-stateless-function': 'off',
},

overrides: [
Expand Down
81 changes: 39 additions & 42 deletions packages/core/src/components/Copy/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import copy from 'copy-to-clipboard';
import IconCopy from '@airbnb/lunar-icons/lib/interface/IconCopy';
import T from '../Translate';
Expand Down Expand Up @@ -27,60 +27,57 @@ export type CopyState = {
};

/** A component for easily copying a string of text to the clipboard. */
export default class Copy extends React.Component<CopyProps, CopyState> {
state = {
copied: false,
};
export default function Copy({
children,
text,
id,
trackingName,
underlined,
prompt,
onCopy,
}: CopyProps) {
const [copied, setCopied] = useState(false);

private handleClick = (event: React.MouseEvent) => {
const { text, onCopy } = this.props;
const handleClick = (event: React.MouseEvent) => {
const result = copy(text);

event.preventDefault();

this.setState({
copied: true,
});
setCopied(true);

if (onCopy) {
onCopy(text, result);
}
};

private handleMouseLeave = () => {
const handleMouseLeave = () => {
window.setTimeout(() => {
this.setState({
copied: false,
});
setCopied(false);
}, 500);
};

render() {
const { prompt, children, id, trackingName, underlined } = this.props;
const element = children || (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<Link trackingName={trackingName} id={id}>
<IconCopy decorative />
</Link>
);
const element = children || (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<Link trackingName={trackingName} id={id}>
<IconCopy decorative />
</Link>
);

return (
<Tooltip
remainOnMouseDown
content={
this.state.copied ? (
<T k="lunar.copy.copied" phrase="Copied!" />
) : (
prompt || <T k="lunar.copy.copyToClipboard" phrase="Copy to clipboard" />
)
}
underlined={underlined}
>
{React.cloneElement(element, {
onClick: this.handleClick,
onMouseLeave: this.handleMouseLeave,
})}
</Tooltip>
);
}
return (
<Tooltip
remainOnMouseDown
content={
copied ? (
<T k="lunar.copy.copied" phrase="Copied!" />
) : (
prompt || <T k="lunar.copy.copyToClipboard" phrase="Copy to clipboard" />
)
}
underlined={underlined}
>
{React.cloneElement(element, {
onClick: handleClick,
onMouseLeave: handleMouseLeave,
})}
</Tooltip>
);
}
80 changes: 38 additions & 42 deletions packages/core/src/components/DateTimeRange/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,54 +21,50 @@ export type DateTimeRangeProps = {
};

/** Display a range between 2 timestamps. */
export default class DateTimeRange extends React.PureComponent<DateTimeRangeProps> {
static defaultProps = {
from: null,
separator: ' – ',
to: null,
};
export default function DateTimeRange({
from,
locale,
separator = ' – ',
timezone,
to,
}: DateTimeRangeProps) {
if (!from || !to) {
return <Empty />;
}

render() {
const { from, locale, separator, timezone, to } = this.props;
const fromTimeStamp = createDateTime(from, { locale, timezone });
const toTimeStamp = createDateTime(to, { locale, timezone });

if (!from || !to) {
return <Empty />;
if (__DEV__) {
if (!fromTimeStamp.isValid || !toTimeStamp.isValid) {
throw new Error('Invalid timestamps passed to `DateTimeRange`.');
}

const fromTimeStamp = createDateTime(from, { locale, timezone });
const toTimeStamp = createDateTime(to, { locale, timezone });

if (__DEV__) {
if (!fromTimeStamp.isValid || !toTimeStamp.isValid) {
throw new Error('Invalid timestamps passed to `DateTimeRange`.');
}

if (toTimeStamp < fromTimeStamp) {
throw new Error('Invalid chronological order of timestamps passed to `DateTimeRange`.');
}
if (toTimeStamp < fromTimeStamp) {
throw new Error('Invalid chronological order of timestamps passed to `DateTimeRange`.');
}
}

const props = { locale, timezone };
let fromFormat = rangeFromDayBundle.get(locale);
let toFormat;

if (fromTimeStamp.year !== toTimeStamp.year) {
fromFormat = dateMediumBundle.get(locale);
toFormat = dateMediumBundle.get(locale);
} else if (fromTimeStamp.month !== toTimeStamp.month) {
toFormat = dateMediumBundle.get(locale);
} else if (fromTimeStamp.day !== toTimeStamp.day) {
toFormat = rangeToDayBundle.get(locale);
} else {
return <DateTime {...props} medium noTime noTimezone at={toTimeStamp} />;
}
const props = { locale, timezone };
let fromFormat = rangeFromDayBundle.get(locale);
let toFormat;

return (
<span>
<DateTime {...props} at={fromTimeStamp} format={fromFormat} />
{separator}
<DateTime {...props} at={toTimeStamp} format={toFormat} />
</span>
);
if (fromTimeStamp.year !== toTimeStamp.year) {
fromFormat = dateMediumBundle.get(locale);
toFormat = dateMediumBundle.get(locale);
} else if (fromTimeStamp.month !== toTimeStamp.month) {
toFormat = dateMediumBundle.get(locale);
} else if (fromTimeStamp.day !== toTimeStamp.day) {
toFormat = rangeToDayBundle.get(locale);
} else {
return <DateTime {...props} medium noTime noTimezone at={toTimeStamp} />;
}

return (
<span>
<DateTime {...props} at={fromTimeStamp} format={fromFormat} />
{separator}
<DateTime {...props} at={toTimeStamp} format={toFormat} />
</span>
);
}
70 changes: 33 additions & 37 deletions packages/core/src/components/ErrorMessage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,45 +48,41 @@ export type ErrorMessageProps = {
};

/** Display an error message from an `Error` instance or API endpoint. */
export default class ErrorMessage extends React.PureComponent<ErrorMessageProps> {
static defaultProps = {
inline: false,
subtitle: null,
title: null,
};

render() {
const { error, inline, title, subtitle, onClose } = this.props;

if (!error) {
return null;
}
export default function ErrorMessage({
error,
inline,
title,
subtitle,
onClose,
}: ErrorMessageProps) {
if (!error) {
return null;
}

const message = subtitle || getErrorMessage(error);
const code = error instanceof Error ? null : error.error_code;
const id = error instanceof Error ? null : error.error_id;
const url = error instanceof Error ? '' : error.error_url;
const message = subtitle || getErrorMessage(error);
const code = error instanceof Error ? null : error.error_code;
const id = error instanceof Error ? null : error.error_id;
const url = error instanceof Error ? '' : error.error_url;

if (inline) {
return <StatusText danger>{message}</StatusText>;
}
if (inline) {
return <StatusText danger>{message}</StatusText>;
}

return (
<Alert
danger
title={title || code || <T k="lunar.error.unknown" phrase="Unknown error" />}
onClose={onClose}
>
{message}
return (
<Alert
danger
title={title || code || <T k="lunar.error.unknown" phrase="Unknown error" />}
onClose={onClose}
>
{message}

{id && (
<Spacing top={1}>
<MutedButton inverted onClick={createRedirectURL(id, url)}>
<T k="lunar.error.viewDetails" phrase="View error details" />
</MutedButton>
</Spacing>
)}
</Alert>
);
}
{id && (
<Spacing top={1}>
<MutedButton inverted onClick={createRedirectURL(id, url)}>
<T k="lunar.error.viewDetails" phrase="View error details" />
</MutedButton>
</Spacing>
)}
</Alert>
);
}
106 changes: 42 additions & 64 deletions packages/core/src/components/FormActions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,71 +35,49 @@ export type FormActionsProps = {
};

/** A pair of action buttons to display at the bottom of a form. */
export default class FormActions extends React.PureComponent<FormActionsProps> {
static defaultProps = {
cancelText: null,
continueText: null,
danger: false,
disabled: false,
hideCancel: false,
processing: false,
processingText: null,
resetText: null,
showReset: false,
small: false,
};
export default function FormActions({
block,
cancelText,
continueText,
danger,
disabled,
hideCancel,
onCancel,
onContinue,
processing,
processingText,
resetText,
showReset,
small,
}: FormActionsProps) {
const Button = danger ? DangerButton : NormalButton;

render() {
const {
block,
cancelText,
continueText,
danger,
disabled,
hideCancel,
onCancel,
onContinue,
processing,
processingText,
resetText,
showReset,
small,
} = this.props;
const Button = danger ? DangerButton : NormalButton;
return (
<ButtonGroup stacked={block}>
<Button
type="submit"
block={block}
disabled={disabled}
loading={processing}
small={small}
onClick={onContinue}
>
{processing
? processingText || <T k="lunar.common.saving" phrase="Saving" />
: continueText || <T k="lunar.common.save" phrase="Save" />}
</Button>

return (
<ButtonGroup stacked={block}>
<Button
type="submit"
block={block}
disabled={disabled}
loading={processing}
small={small}
onClick={onContinue}
>
{processing
? processingText || <T k="lunar.common.saving" phrase="Saving" />
: continueText || <T k="lunar.common.save" phrase="Save" />}
</Button>
{!hideCancel && (
<MutedButton inverted block={block} small={small} disabled={processing} onClick={onCancel}>
{cancelText || <T k="lunar.common.cancel" phrase="Cancel" />}
</MutedButton>
)}

{!hideCancel && (
<MutedButton
inverted
block={block}
small={small}
disabled={processing}
onClick={onCancel}
>
{cancelText || <T k="lunar.common.cancel" phrase="Cancel" />}
</MutedButton>
)}

{showReset && (
<MutedButton inverted block={block} type="reset" small={small} disabled={processing}>
{resetText || <T k="lunar.common.reset" phrase="Reset" />}
</MutedButton>
)}
</ButtonGroup>
);
}
{showReset && (
<MutedButton inverted block={block} type="reset" small={small} disabled={processing}>
{resetText || <T k="lunar.common.reset" phrase="Reset" />}
</MutedButton>
)}
</ButtonGroup>
);
}
Loading

0 comments on commit 25a969a

Please sign in to comment.