Improved button styling

Added toast when collection permissions are saved
Removed usage of setState (old habits die hard)
This commit is contained in:
Tom Moor 2019-01-05 18:23:57 -08:00
parent 713473b7d2
commit f80e4ab04c
11 changed files with 131 additions and 112 deletions

View File

@ -1,60 +1,53 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import { darken } from 'polished';
import { darken, lighten } from 'polished';
const RealButton = styled.button`
display: inline-block;
margin: 0;
padding: 0;
border: 0;
background: ${props => props.theme.primary};
background: ${props => props.theme.blackLight};
color: ${props => props.theme.white};
box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px;
border-radius: 4px;
font-size: 15px;
font-size: 12px;
font-weight: 500;
height: 36px;
text-decoration: none;
text-transform: uppercase;
flex-shrink: 0;
outline: none;
cursor: pointer;
user-select: none;
&::-moz-focus-inner {
padding: 0;
border: 0;
}
&:hover {
background: ${props => darken(0.05, props.theme.primary)};
}
svg {
position: relative;
top: 0.05em;
&:hover {
background: ${props => darken(0.05, props.theme.blackLight)};
}
&:disabled {
opacity: 0.6;
cursor: default;
pointer-events: none;
color: ${props => lighten(0.2, props.theme.blackLight)};
}
${props =>
props.light &&
props.neutral &&
`
color: ${props.theme.slate};
background: transparent;
border: 1px solid ${props.theme.slate};
background: ${props.theme.white};
color: ${props.theme.text};
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px;
border: 1px solid ${props.theme.slateLight};
&:hover {
background: transparent;
color: ${props.theme.slateDark};
border: 1px solid ${props.theme.slateDark};
}
`} ${props =>
props.neutral &&
`
background: ${props.theme.slate};
&:hover {
background: ${darken(0.05, props.theme.slate)};
background: ${darken(0.05, props.theme.white)};
border: 1px solid ${darken(0.05, props.theme.slateLight)};
}
`} ${props =>
props.danger &&
@ -72,7 +65,7 @@ const Label = styled.span`
white-space: nowrap;
text-overflow: ellipsis;
${props => props.hasIcon && 'padding-left: 2px;'};
${props => props.hasIcon && 'padding-left: 4px;'};
`;
const Inner = styled.span`
@ -84,7 +77,7 @@ const Inner = styled.span`
${props =>
props.hasIcon &&
(props.small ? 'padding-left: 6px;' : 'padding-left: 10px;')};
(props.small ? 'padding-left: 6px;' : 'padding-left: 8px;')};
`;
export type Props = {

View File

@ -25,38 +25,40 @@ type Props = {
@observer
class Collections extends React.Component<Props> {
isPreloaded: boolean = !!this.props.collections.orderedData.length;
componentDidMount() {
this.props.collections.fetchPage({ limit: 100 });
}
render() {
const { history, location, collections, ui, documents } = this.props;
const content = (
<Flex column>
<Header>Collections</Header>
{collections.orderedData.map(collection => (
<CollectionLink
key={collection.id}
history={history}
location={location}
collection={collection}
activeDocument={documents.active}
prefetchDocument={documents.prefetchDocument}
ui={ui}
/>
))}
<SidebarLink
onClick={this.props.onCreateCollection}
icon={<PlusIcon />}
>
New collection
</SidebarLink>
</Flex>
);
return (
collections.isLoaded && (
<Fade>
<Flex column>
<Header>Collections</Header>
{collections.orderedData.map(collection => (
<CollectionLink
key={collection.id}
history={history}
location={location}
collection={collection}
activeDocument={documents.active}
prefetchDocument={documents.prefetchDocument}
ui={ui}
/>
))}
<SidebarLink
onClick={this.props.onCreateCollection}
icon={<PlusIcon />}
>
New collection
</SidebarLink>
</Flex>
</Fade>
)
collections.isLoaded &&
(this.isPreloaded ? content : <Fade>{content}</Fade>)
);
}
}

View File

@ -1,5 +1,7 @@
// @flow
import * as React from 'react';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import styled from 'styled-components';
import { CloseIcon } from 'outline-icons';
import Button from './Button';
@ -12,14 +14,10 @@ type Props = {
disabled?: boolean,
};
type State = {
isHidden: boolean,
};
class Tip extends React.Component<Props, State> {
state = {
isHidden: window.localStorage.getItem(this.storageId) === 'hidden',
};
@observer
class Tip extends React.Component<Props> {
@observable
isHidden: boolean = window.localStorage.getItem(this.storageId) === 'hidden';
get storageId() {
return `tip-${this.props.id}`;
@ -27,28 +25,29 @@ class Tip extends React.Component<Props, State> {
hide = () => {
window.localStorage.setItem(this.storageId, 'hidden');
this.setState({ isHidden: true });
this.isHidden = true;
};
render() {
const { children } = this.props;
if (this.props.disabled || this.state.isHidden) return null;
if (this.props.disabled || this.isHidden) return null;
return (
<Wrapper align="center">
<Wrapper align="flex-start">
<span>{children}</span>
<Tooltip tooltip="Hide this message" placement="bottom">
<Button
onClick={this.hide}
icon={<CloseIcon type="close" size={32} color="#FFF" />}
/>
<Close type="close" size={32} color="#000" onClick={this.hide} />
</Tooltip>
</Wrapper>
);
}
}
const Close = styled(CloseIcon)`
margin-top: 8px;
`;
const Wrapper = styled(Flex)`
background: ${props => props.theme.primary};
color: ${props => props.theme.text};

View File

@ -1,5 +1,7 @@
// @flow
import * as React from 'react';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import styled from 'styled-components';
import Tip from './Tip';
import CopyToClipboard from './CopyToClipboard';
@ -9,17 +11,12 @@ type Props = {
team: Team,
};
type State = {
linkCopied: boolean,
};
class TipInvite extends React.Component<Props, State> {
state = {
linkCopied: false,
};
@observer
class TipInvite extends React.Component<Props> {
@observable linkCopied: boolean = false;
handleCopy = () => {
this.setState({ linkCopied: true });
this.linkCopied = true;
};
render() {
@ -35,7 +32,7 @@ class TipInvite extends React.Component<Props, State> {
{' '}
<CopyToClipboard text={team.url} onCopy={this.handleCopy}>
<a>
{this.state.linkCopied
{this.linkCopied
? 'link copied to clipboard!'
: 'copy a link to share.'}
</a>

View File

@ -1,5 +1,7 @@
// @flow
import * as React from 'react';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import styled from 'styled-components';
type Props = {
@ -10,14 +12,10 @@ type Props = {
height?: string,
};
type State = {
isLoaded: boolean,
};
class Frame extends React.Component<Props, State> {
@observer
class Frame extends React.Component<Props> {
mounted: boolean;
state = { isLoaded: false };
@observable isLoaded: boolean = false;
componentDidMount() {
this.mounted = true;
@ -30,7 +28,7 @@ class Frame extends React.Component<Props, State> {
loadIframe = () => {
if (!this.mounted) return;
this.setState({ isLoaded: true });
this.isLoaded = true;
};
render() {
@ -45,7 +43,7 @@ class Frame extends React.Component<Props, State> {
return (
<Rounded width={width} height={height}>
{this.state.isLoaded && (
{this.isLoaded && (
<Component
ref={forwardedRef}
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"

View File

@ -142,27 +142,17 @@ class CollectionScene extends React.Component<Props> {
{collection ? (
<React.Fragment>
<PageTitle title={collection.name} />
<Heading>
{collection.private ? (
<PrivateCollectionIcon
color={collection.color}
size={40}
expanded
/>
) : (
<CollectionIcon color={collection.color} size={40} expanded />
)}{' '}
{collection.name}
</Heading>
{collection.isEmpty ? (
<React.Fragment>
<Centered column>
<HelpText>
Collections are for grouping your knowledge base. Get started
by creating a new document.
<strong>{collection.name}</strong> doesnt contain any
documents yet.<br />Get started by creating a new one!
</HelpText>
<Wrapper>
<Link to={newDocumentUrl(collection)}>
<Button>Create new document</Button>
<Button icon={<NewDocumentIcon color="#FFF" />}>
Create a document
</Button>
</Link>&nbsp;&nbsp;
{collection.private && (
<Button onClick={this.onPermissions} neutral>
@ -180,9 +170,26 @@ class CollectionScene extends React.Component<Props> {
onSubmit={this.handlePermissionsModalClose}
/>
</Modal>
</React.Fragment>
</Centered>
) : (
<React.Fragment>
<Heading>
{collection.private ? (
<PrivateCollectionIcon
color={collection.color}
size={40}
expanded
/>
) : (
<CollectionIcon
color={collection.color}
size={40}
expanded
/>
)}{' '}
{collection.name}
</Heading>
{collection.description && (
<RichMarkdownEditor
key={collection.description}
@ -220,6 +227,13 @@ class CollectionScene extends React.Component<Props> {
}
}
const Centered = styled(Flex)`
text-align: center;
margin: 40vh auto 0;
max-width: 380px;
transform: translateY(-50%);
`;
const TinyPinIcon = styled(PinIcon)`
position: relative;
top: 4px;
@ -227,6 +241,7 @@ const TinyPinIcon = styled(PinIcon)`
`;
const Wrapper = styled(Flex)`
justify-content: center;
margin: 10px 0;
`;

View File

@ -45,12 +45,12 @@ class CollectionDelete extends React.Component<Props> {
<Flex column>
<form onSubmit={this.handleSubmit}>
<HelpText>
Are you sure? Deleting the <strong>{collection.name}</strong>{' '}
collection is permanent and will also delete all of the documents
within it, so be careful with that.
Are you sure about that? Deleting the{' '}
<strong>{collection.name}</strong> collection is permanent and will
also delete all of the documents within it, so be extra careful.
</HelpText>
<Button type="submit" danger>
{this.isDeleting ? 'Deleting…' : 'Delete'}
{this.isDeleting ? 'Deleting…' : 'Im sure  Delete'}
</Button>
</form>
</Flex>

View File

@ -27,7 +27,8 @@ type Props = {
@observer
class CollectionPermissions extends React.Component<Props> {
@observable isSaving: boolean;
@observable isEdited: boolean = false;
@observable isSaving: boolean = false;
@observable filter: string;
componentDidMount() {
@ -35,10 +36,17 @@ class CollectionPermissions extends React.Component<Props> {
this.props.collection.fetchUsers();
}
componentWillUnmount() {
if (this.isEdited) {
this.props.ui.showToast('Permissions updated');
}
}
handlePrivateChange = async (ev: SyntheticInputEvent<*>) => {
const { collection } = this.props;
try {
this.isEdited = true;
collection.private = ev.target.checked;
await collection.save();
@ -53,6 +61,7 @@ class CollectionPermissions extends React.Component<Props> {
handleAddUser = user => {
try {
this.isEdited = true;
this.props.collection.addUser(user);
} catch (err) {
this.props.ui.showToast('Could not add user');
@ -61,6 +70,7 @@ class CollectionPermissions extends React.Component<Props> {
handleRemoveUser = user => {
try {
this.isEdited = true;
this.props.collection.removeUser(user);
} catch (err) {
this.props.ui.showToast('Could not remove user');

View File

@ -18,7 +18,7 @@ const UserListItem = ({ user, onAdd, showAdd }: Props) => {
image={<Avatar src={user.avatarUrl} size={32} />}
actions={
showAdd ? (
<Button type="button" onClick={onAdd}>
<Button type="button" onClick={onAdd} neutral>
Invite
</Button>
) : (

View File

@ -45,11 +45,12 @@ class DocumentDelete extends React.Component<Props> {
<Flex column>
<form onSubmit={this.handleSubmit}>
<HelpText>
Are you sure? Deleting the <strong>{document.title}</strong>{' '}
document is permanent and will also delete all of its history.
Are you sure about that? Deleting the{' '}
<strong>{document.title}</strong> document is permanent, will delete
all of its history, and any child documents.
</HelpText>
<Button type="submit" danger>
{this.isDeleting ? 'Deleting…' : 'Delete'}
{this.isDeleting ? 'Deleting…' : 'Im sure  Delete'}
</Button>
</form>
</Flex>

View File

@ -17,7 +17,11 @@ function SlackButton({ state, scopes, redirectUri, label }: Props) {
(window.location.href = slackAuth(state, scopes, redirectUri));
return (
<Button onClick={handleClick} icon={<SpacedSlackLogo size={24} />} neutral>
<Button
onClick={handleClick}
icon={<SpacedSlackLogo size={24} fill="#000" />}
neutral
>
{label ? (
label
) : (