2018-03-26 23:32:43 +02:00
|
|
|
// @flow
|
2019-06-28 09:27:55 +02:00
|
|
|
import type { Node } from 'react';
|
2019-11-15 22:53:31 +01:00
|
|
|
import React, { forwardRef, useRef } from 'react';
|
2018-03-26 23:32:43 +02:00
|
|
|
import Icon from 'component/common/icon';
|
|
|
|
import classnames from 'classnames';
|
2019-04-04 23:05:23 +02:00
|
|
|
import { NavLink } from 'react-router-dom';
|
2019-12-02 18:30:08 +01:00
|
|
|
import { formatLbryUrlForWeb } from 'util/url';
|
2019-10-15 20:53:55 +02:00
|
|
|
import * as PAGES from 'constants/pages';
|
2019-11-15 22:53:31 +01:00
|
|
|
import useCombinedRefs from 'effects/use-combined-refs';
|
2018-03-26 23:32:43 +02:00
|
|
|
|
|
|
|
type Props = {
|
2019-06-12 04:22:21 +02:00
|
|
|
id: ?string,
|
2018-03-26 23:32:43 +02:00
|
|
|
href: ?string,
|
|
|
|
title: ?string,
|
|
|
|
label: ?string,
|
2020-04-03 01:09:59 +02:00
|
|
|
largestLabel: ?string,
|
2018-03-26 23:32:43 +02:00
|
|
|
icon: ?string,
|
|
|
|
iconRight: ?string,
|
|
|
|
disabled: ?boolean,
|
2019-06-28 09:27:55 +02:00
|
|
|
children: ?Node,
|
2018-03-26 23:32:43 +02:00
|
|
|
navigate: ?string,
|
|
|
|
className: ?string,
|
|
|
|
description: ?string,
|
|
|
|
type: string,
|
|
|
|
button: ?string, // primary, secondary, alt, link
|
2018-12-19 06:44:53 +01:00
|
|
|
iconSize?: number,
|
2019-06-28 09:27:55 +02:00
|
|
|
iconColor?: string,
|
2019-03-28 17:53:13 +01:00
|
|
|
activeClass?: string,
|
2019-06-05 08:07:53 +02:00
|
|
|
innerRef: ?any,
|
2020-11-17 20:10:14 +01:00
|
|
|
authSrc?: string,
|
2019-06-05 08:07:53 +02:00
|
|
|
// Events
|
|
|
|
onClick: ?(any) => any,
|
|
|
|
onMouseEnter: ?(any) => any,
|
|
|
|
onMouseLeave: ?(any) => any,
|
2019-10-15 20:53:55 +02:00
|
|
|
pathname: string,
|
|
|
|
emailVerified: boolean,
|
|
|
|
requiresAuth: ?boolean,
|
|
|
|
myref: any,
|
|
|
|
dispatch: any,
|
2020-05-12 11:22:36 +02:00
|
|
|
'aria-label'?: string,
|
2021-06-18 00:52:21 +02:00
|
|
|
user: ?User,
|
2021-07-01 21:40:13 +02:00
|
|
|
disableOnFatal?: boolean,
|
|
|
|
fatal: boolean,
|
2018-03-26 23:32:43 +02:00
|
|
|
};
|
|
|
|
|
2019-06-28 09:27:55 +02:00
|
|
|
// use forwardRef to allow consumers to pass refs to the button content if they want to
|
|
|
|
// flow requires forwardRef have default type arguments passed to it
|
|
|
|
const Button = forwardRef<any, {}>((props: Props, ref: any) => {
|
|
|
|
const {
|
|
|
|
type = 'button',
|
|
|
|
onClick,
|
|
|
|
href,
|
|
|
|
title,
|
|
|
|
label,
|
2020-04-03 01:09:59 +02:00
|
|
|
largestLabel,
|
2019-06-28 09:27:55 +02:00
|
|
|
icon,
|
2020-11-17 20:10:14 +01:00
|
|
|
|
2019-06-28 09:27:55 +02:00
|
|
|
// This should rarely be used. Regular buttons should just use `icon`
|
|
|
|
// `iconRight` is used for the header (home) button with the LBRY icon and external links that are displayed inline
|
|
|
|
iconRight,
|
|
|
|
disabled,
|
|
|
|
children,
|
|
|
|
navigate,
|
|
|
|
className,
|
|
|
|
description,
|
|
|
|
button,
|
|
|
|
iconSize,
|
|
|
|
iconColor,
|
|
|
|
activeClass,
|
2019-10-15 20:53:55 +02:00
|
|
|
emailVerified,
|
|
|
|
requiresAuth,
|
|
|
|
myref,
|
|
|
|
dispatch, // <button> doesn't know what to do with dispatch
|
|
|
|
pathname,
|
2021-06-18 00:52:21 +02:00
|
|
|
user,
|
2020-11-17 20:10:14 +01:00
|
|
|
authSrc,
|
2021-07-01 21:40:13 +02:00
|
|
|
disableOnFatal,
|
|
|
|
fatal,
|
2019-06-28 09:27:55 +02:00
|
|
|
...otherProps
|
|
|
|
} = props;
|
2018-03-26 23:32:43 +02:00
|
|
|
|
2021-07-01 21:40:13 +02:00
|
|
|
const disable = disabled || (user === null && requiresAuth) || (fatal && disableOnFatal);
|
2021-06-18 00:52:21 +02:00
|
|
|
|
2019-06-28 09:27:55 +02:00
|
|
|
const combinedClassName = classnames(
|
|
|
|
'button',
|
|
|
|
button
|
|
|
|
? {
|
|
|
|
'button--primary': button === 'primary',
|
|
|
|
'button--secondary': button === 'secondary',
|
|
|
|
'button--alt': button === 'alt',
|
|
|
|
'button--inverse': button === 'inverse',
|
|
|
|
'button--close': button === 'close',
|
2021-06-18 00:52:21 +02:00
|
|
|
'button--disabled': disable,
|
2019-06-28 09:27:55 +02:00
|
|
|
'button--link': button === 'link',
|
|
|
|
}
|
|
|
|
: 'button--no-style',
|
|
|
|
className
|
|
|
|
);
|
2018-03-26 23:32:43 +02:00
|
|
|
|
2019-11-15 22:53:31 +01:00
|
|
|
const innerRef = useRef(null);
|
|
|
|
const combinedRef = useCombinedRefs(ref, innerRef, myref);
|
2020-04-01 20:43:50 +02:00
|
|
|
const size = iconSize || (!label && !children) ? 18 : undefined; // Fall back to default
|
2019-11-15 22:53:31 +01:00
|
|
|
|
2019-06-28 09:27:55 +02:00
|
|
|
const content = (
|
2020-04-11 02:46:03 +02:00
|
|
|
<span className="button__content">
|
|
|
|
{icon && <Icon icon={icon} iconColor={iconColor} size={iconSize} />}
|
2020-04-11 02:24:23 +02:00
|
|
|
|
2020-09-23 17:00:10 +02:00
|
|
|
{!largestLabel && label && (
|
|
|
|
<span dir="auto" className="button__label">
|
|
|
|
{label}
|
|
|
|
</span>
|
|
|
|
)}
|
2020-04-17 22:33:02 +02:00
|
|
|
|
|
|
|
{/* largestLabel is used when a single button has two different labels based on hover state */}
|
|
|
|
{largestLabel && (
|
2020-08-20 13:27:28 +02:00
|
|
|
<div dir="auto" className="button__label" style={{ position: 'relative' }}>
|
2020-04-11 02:24:23 +02:00
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
position: 'relative',
|
|
|
|
left: '50%',
|
|
|
|
top: '50%',
|
|
|
|
transform: `translate(-50%, 0%)`,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<span style={{ visibility: 'hidden' }}>
|
|
|
|
{largestLabel || label}
|
2020-04-04 01:29:39 +02:00
|
|
|
<div
|
|
|
|
style={{
|
2020-04-11 02:24:23 +02:00
|
|
|
position: 'absolute',
|
2020-04-04 01:29:39 +02:00
|
|
|
left: '50%',
|
2020-04-11 02:24:23 +02:00
|
|
|
top: '50%',
|
2020-04-04 01:29:39 +02:00
|
|
|
transform: `translate(-50%, -50%)`,
|
|
|
|
}}
|
|
|
|
>
|
2020-04-12 21:10:47 +02:00
|
|
|
<span style={{ visibility: 'visible' }}>{label}</span>
|
2020-04-04 01:29:39 +02:00
|
|
|
</div>
|
2020-04-11 02:24:23 +02:00
|
|
|
</span>
|
|
|
|
</div>
|
2020-04-04 01:29:39 +02:00
|
|
|
</div>
|
2020-04-11 02:24:23 +02:00
|
|
|
)}
|
|
|
|
|
2019-06-28 09:27:55 +02:00
|
|
|
{children && children}
|
2020-04-01 20:43:50 +02:00
|
|
|
{iconRight && <Icon icon={iconRight} iconColor={iconColor} size={size} />}
|
2019-06-28 09:27:55 +02:00
|
|
|
</span>
|
|
|
|
);
|
2020-06-04 16:56:57 +02:00
|
|
|
|
2020-09-23 17:00:10 +02:00
|
|
|
if (href || (navigate && navigate.startsWith('http'))) {
|
|
|
|
// TODO: replace the below with an outbound link tracker for matomo
|
2019-06-28 09:27:55 +02:00
|
|
|
return (
|
2020-09-23 17:00:10 +02:00
|
|
|
<a
|
|
|
|
target="_blank"
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
href={href || navigate}
|
|
|
|
className={combinedClassName}
|
2020-10-02 06:12:06 +02:00
|
|
|
title={title}
|
2020-09-23 17:00:10 +02:00
|
|
|
onClick={onClick}
|
|
|
|
>
|
2019-06-28 09:27:55 +02:00
|
|
|
{content}
|
2020-06-02 22:52:34 +02:00
|
|
|
</a>
|
2018-03-26 23:32:43 +02:00
|
|
|
);
|
2019-06-28 09:27:55 +02:00
|
|
|
}
|
2018-03-26 23:32:43 +02:00
|
|
|
|
2019-06-28 09:27:55 +02:00
|
|
|
// Handle lbry:// uris passed in, or already formatted web urls
|
|
|
|
let path = navigate;
|
|
|
|
if (path) {
|
|
|
|
if (path.startsWith('lbry://')) {
|
2019-12-02 18:30:08 +01:00
|
|
|
path = formatLbryUrlForWeb(path);
|
2019-06-28 09:27:55 +02:00
|
|
|
} else if (!path.startsWith('/')) {
|
|
|
|
// Force a leading slash so new paths aren't appended on to the current path
|
|
|
|
path = `/${path}`;
|
2019-03-28 17:53:13 +01:00
|
|
|
}
|
2018-03-26 23:32:43 +02:00
|
|
|
}
|
2019-06-28 09:27:55 +02:00
|
|
|
|
2020-05-12 11:22:36 +02:00
|
|
|
// Try to generate a tooltip using available text and display it through
|
|
|
|
// the 'title' mechanism, but only if it isn't being used.
|
|
|
|
let defaultTooltip;
|
|
|
|
if (!title) {
|
|
|
|
if (props['aria-label']) {
|
|
|
|
defaultTooltip = props['aria-label'];
|
|
|
|
} else if (description) {
|
|
|
|
defaultTooltip = description;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-15 20:53:55 +02:00
|
|
|
if (requiresAuth && !emailVerified) {
|
2020-11-17 20:10:14 +01:00
|
|
|
let redirectUrl = `/$/${PAGES.AUTH}?redirect=${pathname}`;
|
|
|
|
|
|
|
|
if (authSrc) {
|
|
|
|
redirectUrl += `&src=${authSrc}`;
|
|
|
|
}
|
|
|
|
|
2019-10-15 20:53:55 +02:00
|
|
|
return (
|
|
|
|
<NavLink
|
|
|
|
exact
|
2021-06-18 00:52:21 +02:00
|
|
|
onClick={(e) => {
|
2019-10-23 20:59:33 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
}}
|
2020-11-17 20:10:14 +01:00
|
|
|
to={redirectUrl}
|
2020-05-12 11:22:36 +02:00
|
|
|
title={title || defaultTooltip}
|
2021-06-18 00:52:21 +02:00
|
|
|
disabled={disable}
|
2019-10-15 20:53:55 +02:00
|
|
|
className={combinedClassName}
|
|
|
|
activeClassName={activeClass}
|
|
|
|
>
|
|
|
|
{content}
|
|
|
|
</NavLink>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-06-28 09:27:55 +02:00
|
|
|
return path ? (
|
|
|
|
<NavLink
|
|
|
|
exact
|
|
|
|
to={path}
|
2020-05-12 11:22:36 +02:00
|
|
|
title={title || defaultTooltip}
|
2021-06-18 00:52:21 +02:00
|
|
|
disabled={disable}
|
|
|
|
onClick={(e) => {
|
2019-06-28 09:27:55 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
if (onClick) {
|
|
|
|
onClick();
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
className={combinedClassName}
|
|
|
|
activeClassName={activeClass}
|
2020-01-06 19:32:35 +01:00
|
|
|
{...otherProps}
|
2019-06-28 09:27:55 +02:00
|
|
|
>
|
|
|
|
{content}
|
|
|
|
</NavLink>
|
|
|
|
) : (
|
|
|
|
<button
|
2019-11-15 22:53:31 +01:00
|
|
|
ref={combinedRef}
|
2020-05-12 11:22:36 +02:00
|
|
|
title={title || defaultTooltip}
|
2019-06-28 09:27:55 +02:00
|
|
|
aria-label={description || label || title}
|
|
|
|
className={combinedClassName}
|
2021-06-18 00:52:21 +02:00
|
|
|
onClick={(e) => {
|
2019-08-13 07:35:13 +02:00
|
|
|
if (onClick) {
|
|
|
|
e.stopPropagation();
|
|
|
|
onClick(e);
|
|
|
|
}
|
|
|
|
}}
|
2021-06-18 00:52:21 +02:00
|
|
|
disabled={disable}
|
2019-06-28 09:27:55 +02:00
|
|
|
type={type}
|
|
|
|
{...otherProps}
|
|
|
|
>
|
|
|
|
{content}
|
|
|
|
</button>
|
|
|
|
);
|
|
|
|
});
|
2018-03-26 23:32:43 +02:00
|
|
|
|
|
|
|
export default Button;
|