Files
outline/app/components/Button.tsx
Tom Moor 15b1069bcc chore: Move to Typescript (#2783)
This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously.

closes #1282
2021-11-29 06:40:55 -08:00

172 lines
4.0 KiB
TypeScript

import { ExpandedIcon } from "outline-icons";
import { darken } from "polished";
import * as React from "react";
import styled from "styled-components";
const RealButton = styled.button<{
fullwidth?: boolean;
borderOnHover?: boolean;
neutral?: boolean;
danger?: boolean;
iconColor?: string;
}>`
display: ${(props) => (props.fullwidth ? "block" : "inline-block")};
width: ${(props) => (props.fullwidth ? "100%" : "auto")};
margin: 0;
padding: 0;
border: 0;
background: ${(props) => props.theme.buttonBackground};
color: ${(props) => props.theme.buttonText};
box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
height: 32px;
text-decoration: none;
flex-shrink: 0;
cursor: pointer;
user-select: none;
${(props) =>
!props.borderOnHover &&
`
svg {
fill: ${props.iconColor || props.theme.buttonText};
}
`}
&::-moz-focus-inner {
padding: 0;
border: 0;
}
&:hover:not(:disabled) {
background: ${(props) => darken(0.05, props.theme.buttonBackground)};
}
&:disabled {
cursor: default;
pointer-events: none;
color: ${(props) => props.theme.white50};
svg {
fill: ${(props) => props.theme.white50};
}
}
${(props) =>
props.neutral &&
`
background: ${props.theme.buttonNeutralBackground};
color: ${props.theme.buttonNeutralText};
box-shadow: ${
props.borderOnHover
? "none"
: `rgba(0, 0, 0, 0.07) 0px 1px 2px, ${props.theme.buttonNeutralBorder} 0 0 0 1px inset`
};
${
props.borderOnHover
? ""
: `svg {
fill: ${props.iconColor || props.theme.buttonNeutralText};
}`
}
&:hover:not(:disabled) {
background: ${
props.borderOnHover
? props.theme.buttonNeutralBackground
: darken(0.05, props.theme.buttonNeutralBackground)
};
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, ${
props.theme.buttonNeutralBorder
} 0 0 0 1px inset;
}
&:disabled {
color: ${props.theme.textTertiary};
svg {
fill: ${props.theme.textTertiary};
}
}
`}
${(props) =>
props.danger &&
`
background: ${props.theme.danger};
color: ${props.theme.white};
&:hover {
background: ${darken(0.05, props.theme.danger)};
}
`};
`;
const Label = styled.span<{ hasIcon?: boolean }>`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
${(props) => props.hasIcon && "padding-left: 4px;"};
`;
export const Inner = styled.span<{
disclosure?: boolean;
hasIcon?: boolean;
hasText?: boolean;
}>`
display: flex;
padding: 0 8px;
padding-right: ${(props) => (props.disclosure ? 2 : 8)}px;
line-height: ${(props) => (props.hasIcon ? 24 : 32)}px;
justify-content: center;
align-items: center;
min-height: 32px;
${(props) => props.hasIcon && props.hasText && "padding-left: 4px;"};
${(props) => props.hasIcon && !props.hasText && "padding: 0 4px;"};
`;
export type Props<T> = {
icon?: React.ReactNode;
iconColor?: string;
children?: React.ReactNode;
disclosure?: boolean;
neutral?: boolean;
danger?: boolean;
primary?: boolean;
fullwidth?: boolean;
as?: T;
to?: string;
borderOnHover?: boolean;
href?: string;
"data-on"?: string;
"data-event-category"?: string;
"data-event-action"?: string;
};
const Button = <T extends React.ElementType = "button">(
props: Props<T> & React.ComponentPropsWithoutRef<T>,
ref: React.Ref<HTMLButtonElement>
) => {
const { type, icon, children, value, disclosure, neutral, ...rest } = props;
const hasText = children !== undefined || value !== undefined;
const hasIcon = icon !== undefined;
return (
<RealButton type={type || "button"} ref={ref} neutral={neutral} {...rest}>
<Inner hasIcon={hasIcon} hasText={hasText} disclosure={disclosure}>
{hasIcon && icon}
{hasText && <Label hasIcon={hasIcon}>{children || value}</Label>}
{disclosure && <ExpandedIcon />}
</Inner>
</RealButton>
);
};
export default React.forwardRef(Button);