590feab1d1
The "Report Content" button was the impetus for this, as there was no way to know what that button is for unless the user clicked it (or smart enough to decipher the icon). 3465
231 lines
5.9 KiB
JavaScript
231 lines
5.9 KiB
JavaScript
// @flow
|
|
import type { Node } from 'react';
|
|
import React, { forwardRef, useRef } from 'react';
|
|
import Icon from 'component/common/icon';
|
|
import classnames from 'classnames';
|
|
import { NavLink } from 'react-router-dom';
|
|
import { formatLbryUrlForWeb } from 'util/url';
|
|
import * as PAGES from 'constants/pages';
|
|
import useCombinedRefs from 'effects/use-combined-refs';
|
|
|
|
type Props = {
|
|
id: ?string,
|
|
href: ?string,
|
|
title: ?string,
|
|
label: ?string,
|
|
largestLabel: ?string,
|
|
icon: ?string,
|
|
iconRight: ?string,
|
|
disabled: ?boolean,
|
|
children: ?Node,
|
|
navigate: ?string,
|
|
className: ?string,
|
|
description: ?string,
|
|
type: string,
|
|
button: ?string, // primary, secondary, alt, link
|
|
iconSize?: number,
|
|
iconColor?: string,
|
|
activeClass?: string,
|
|
innerRef: ?any,
|
|
// Events
|
|
onClick: ?(any) => any,
|
|
onMouseEnter: ?(any) => any,
|
|
onMouseLeave: ?(any) => any,
|
|
pathname: string,
|
|
emailVerified: boolean,
|
|
requiresAuth: ?boolean,
|
|
myref: any,
|
|
dispatch: any,
|
|
'aria-label'?: string,
|
|
};
|
|
|
|
// 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,
|
|
largestLabel,
|
|
icon,
|
|
// 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,
|
|
emailVerified,
|
|
requiresAuth,
|
|
myref,
|
|
dispatch, // <button> doesn't know what to do with dispatch
|
|
pathname,
|
|
...otherProps
|
|
} = props;
|
|
|
|
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',
|
|
'button--disabled': disabled,
|
|
'button--link': button === 'link',
|
|
}
|
|
: 'button--no-style',
|
|
className
|
|
);
|
|
|
|
const innerRef = useRef(null);
|
|
const combinedRef = useCombinedRefs(ref, innerRef, myref);
|
|
const size = iconSize || (!label && !children) ? 18 : undefined; // Fall back to default
|
|
|
|
const content = (
|
|
<span className="button__content">
|
|
{icon && <Icon icon={icon} iconColor={iconColor} size={iconSize} />}
|
|
|
|
{!largestLabel && label && (
|
|
<span dir="auto" className="button__label">
|
|
{label}
|
|
</span>
|
|
)}
|
|
|
|
{/* largestLabel is used when a single button has two different labels based on hover state */}
|
|
{largestLabel && (
|
|
<div dir="auto" className="button__label" style={{ position: 'relative' }}>
|
|
<div
|
|
style={{
|
|
position: 'relative',
|
|
left: '50%',
|
|
top: '50%',
|
|
transform: `translate(-50%, 0%)`,
|
|
}}
|
|
>
|
|
<span style={{ visibility: 'hidden' }}>
|
|
{largestLabel || label}
|
|
<div
|
|
style={{
|
|
position: 'absolute',
|
|
left: '50%',
|
|
top: '50%',
|
|
transform: `translate(-50%, -50%)`,
|
|
}}
|
|
>
|
|
<span style={{ visibility: 'visible' }}>{label}</span>
|
|
</div>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{children && children}
|
|
{iconRight && <Icon icon={iconRight} iconColor={iconColor} size={size} />}
|
|
</span>
|
|
);
|
|
|
|
if (href || (navigate && navigate.startsWith('http'))) {
|
|
// TODO: replace the below with an outbound link tracker for matomo
|
|
return (
|
|
<a
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
href={href || navigate}
|
|
className={combinedClassName}
|
|
title={title}
|
|
onClick={onClick}
|
|
>
|
|
{content}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
// Handle lbry:// uris passed in, or already formatted web urls
|
|
let path = navigate;
|
|
if (path) {
|
|
if (path.startsWith('lbry://')) {
|
|
path = formatLbryUrlForWeb(path);
|
|
} else if (!path.startsWith('/')) {
|
|
// Force a leading slash so new paths aren't appended on to the current path
|
|
path = `/${path}`;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
if (requiresAuth && !emailVerified) {
|
|
return (
|
|
<NavLink
|
|
exact
|
|
onClick={e => {
|
|
e.stopPropagation();
|
|
}}
|
|
to={`/$/${PAGES.AUTH}?redirect=${pathname}`}
|
|
title={title || defaultTooltip}
|
|
disabled={disabled}
|
|
className={combinedClassName}
|
|
activeClassName={activeClass}
|
|
>
|
|
{content}
|
|
</NavLink>
|
|
);
|
|
}
|
|
|
|
return path ? (
|
|
<NavLink
|
|
exact
|
|
to={path}
|
|
title={title || defaultTooltip}
|
|
disabled={disabled}
|
|
onClick={e => {
|
|
e.stopPropagation();
|
|
if (onClick) {
|
|
onClick();
|
|
}
|
|
}}
|
|
className={combinedClassName}
|
|
activeClassName={activeClass}
|
|
{...otherProps}
|
|
>
|
|
{content}
|
|
</NavLink>
|
|
) : (
|
|
<button
|
|
ref={combinedRef}
|
|
title={title || defaultTooltip}
|
|
aria-label={description || label || title}
|
|
className={combinedClassName}
|
|
onClick={e => {
|
|
if (onClick) {
|
|
e.stopPropagation();
|
|
onClick(e);
|
|
}
|
|
}}
|
|
disabled={disabled}
|
|
type={type}
|
|
{...otherProps}
|
|
>
|
|
{content}
|
|
</button>
|
|
);
|
|
});
|
|
|
|
export default Button;
|