Improved button styling
Added toast when collection permissions are saved Removed usage of setState (old habits die hard)
This commit is contained in:
parent
713473b7d2
commit
f80e4ab04c
@ -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 = {
|
||||
|
@ -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>)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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> doesn’t 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>
|
||||
{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;
|
||||
`;
|
||||
|
||||
|
@ -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…' : 'I’m sure – Delete'}
|
||||
</Button>
|
||||
</form>
|
||||
</Flex>
|
||||
|
@ -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');
|
||||
|
@ -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>
|
||||
) : (
|
||||
|
@ -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…' : 'I’m sure – Delete'}
|
||||
</Button>
|
||||
</form>
|
||||
</Flex>
|
||||
|
@ -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
|
||||
) : (
|
||||
|
Reference in New Issue
Block a user