Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add indicator of starred status when viewing a document #1785

Merged
merged 2 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 13 additions & 44 deletions app/components/DocumentListItem.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// @flow
import { observer } from "mobx-react";
import { StarredIcon, PlusIcon } from "outline-icons";
import { PlusIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom";
import styled, { css, withTheme } from "styled-components";
import styled, { css } from "styled-components";
import Document from "models/Document";
import Badge from "components/Badge";
import Button from "components/Button";
import DocumentMeta from "components/DocumentMeta";
import EventBoundary from "components/EventBoundary";
import Flex from "components/Flex";
import Highlight from "components/Highlight";
import StarButton, { AnimatedStar } from "components/Star";
import Tooltip from "components/Tooltip";
import useCurrentUser from "hooks/useCurrentUser";
import DocumentMenu from "menus/DocumentMenu";
Expand Down Expand Up @@ -52,24 +53,6 @@ function DocumentListItem(props: Props) {
context,
} = props;

const handleStar = React.useCallback(
(ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();
document.star();
},
[document]
);

const handleUnstar = React.useCallback(
(ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();
document.unstar();
},
[document]
);

const handleNewFromTemplate = React.useCallback(
(ev: SyntheticEvent<>) => {
ev.preventDefault();
Expand All @@ -90,7 +73,8 @@ function DocumentListItem(props: Props) {

return (
<DocumentLink
menuOpen={menuOpen}
$isStarred={document.isStarred}
$menuOpen={menuOpen}
to={{
pathname: document.url,
state: { title: document.titleWithDefault },
Expand All @@ -103,11 +87,7 @@ function DocumentListItem(props: Props) {
)}
{!document.isDraft && !document.isArchived && !document.isTemplate && (
<Actions>
{document.isStarred ? (
<StyledStar onClick={handleUnstar} solid />
) : (
<StyledStar onClick={handleStar} />
)}
<StarButton document={document} />
</Actions>
)}
{document.isDraft && showDraft && (
Expand Down Expand Up @@ -157,21 +137,6 @@ function DocumentListItem(props: Props) {
);
}

const StyledStar = withTheme(styled(({ solid, theme, ...props }) => (
<StarredIcon color={theme.text} {...props} />
))`
flex-shrink: 0;
opacity: ${(props) => (props.solid ? "1 !important" : 0)};
transition: all 100ms ease-in-out;

&:hover {
transform: scale(1.1);
}
&:active {
transform: scale(0.95);
}
`);

const SecondaryActions = styled(Flex)`
align-items: center;
position: absolute;
Expand All @@ -195,6 +160,10 @@ const DocumentLink = styled(Link)`
opacity: 0;
}

${AnimatedStar} {
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
}

&:hover,
&:active,
&:focus {
Expand All @@ -204,7 +173,7 @@ const DocumentLink = styled(Link)`
opacity: 1;
}

${StyledStar} {
${AnimatedStar} {
opacity: 0.5;

&:hover {
Expand All @@ -214,15 +183,15 @@ const DocumentLink = styled(Link)`
}

${(props) =>
props.menuOpen &&
props.$menuOpen &&
css`
background: ${(props) => props.theme.listItemHoverBackground};

${SecondaryActions} {
opacity: 1;
}

${StyledStar} {
${AnimatedStar} {
opacity: 0.5;
}
`}
Expand Down
10 changes: 5 additions & 5 deletions app/components/NudeButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import * as React from "react";
import styled from "styled-components";

const Button = styled.button`
width: 24px;
height: 24px;
width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
background: none;
border-radius: 4px;
line-height: 0;
Expand All @@ -14,6 +14,6 @@ const Button = styled.button`
user-select: none;
`;

export default React.forwardRef<any, typeof Button>((props, ref) => (
<Button {...props} ref={ref} />
));
export default React.forwardRef<any, typeof Button>(
({ size = 24, ...props }, ref) => <Button size={size} {...props} ref={ref} />
);
59 changes: 59 additions & 0 deletions app/components/Star.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// @flow
import { StarredIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import Document from "models/Document";
import NudeButton from "./NudeButton";

type Props = {|
document: Document,
size?: number,
|};

function Star({ size, document, ...rest }: Props) {
const handleClick = React.useCallback(
(ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();

if (document.isStarred) {
document.unstar();
} else {
document.star();
}
},
[document]
);

if (!document) {
return null;
}

return (
<Button onClick={handleClick} size={size} {...rest}>
<AnimatedStar
solid={document.isStarred}
size={size}
color="currentColor"
/>
</Button>
);
}

const Button = styled(NudeButton)`
color: ${(props) => props.theme.text};
`;

export const AnimatedStar = styled(StarredIcon)`
flex-shrink: 0;
transition: all 100ms ease-in-out;

&:hover {
transform: scale(1.1);
}
&:active {
transform: scale(0.95);
}
`;

export default Star;
57 changes: 45 additions & 12 deletions app/scenes/Document/components/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import DocumentMetaWithViews from "components/DocumentMetaWithViews";
import Editor from "components/Editor";
import Flex from "components/Flex";
import HoverPreview from "components/HoverPreview";
import Star, { AnimatedStar } from "components/Star";
import { isMetaKey } from "utils/keyboard";
import { documentHistoryUrl } from "utils/routeHelpers";

Expand Down Expand Up @@ -98,23 +99,35 @@ class DocumentEditor extends React.Component<Props> {
readOnly,
innerRef,
} = this.props;

const { emoji } = parseTitle(title);
const startsWithEmojiAndSpace = !!(emoji && title.startsWith(`${emoji} `));
const normalizedTitle =
!title && readOnly ? document.titleWithDefault : title;

return (
<Flex auto column>
<Title
type="text"
onChange={onChangeTitle}
onKeyDown={this.handleTitleKeyDown}
placeholder={document.placeholder}
value={!title && readOnly ? document.titleWithDefault : title}
style={startsWithEmojiAndSpace ? { marginLeft: "-1.2em" } : undefined}
readOnly={readOnly}
disabled={readOnly}
autoFocus={!title}
maxLength={MAX_TITLE_LENGTH}
/>
{readOnly ? (
<Title
as="div"
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
$isStarred={document.isStarred}
>
<span>{normalizedTitle}</span>{" "}
{!isShare && <StarButton document={document} size={32} />}
</Title>
) : (
<Title
type="text"
onChange={onChangeTitle}
onKeyDown={this.handleTitleKeyDown}
placeholder={document.placeholder}
value={normalizedTitle}
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
autoFocus={!title}
maxLength={MAX_TITLE_LENGTH}
/>
)}
<DocumentMetaWithViews
isDraft={isDraft}
document={document}
Expand Down Expand Up @@ -142,11 +155,17 @@ class DocumentEditor extends React.Component<Props> {
}
}

const StarButton = styled(Star)`
position: relative;
top: 4px;
`;

const Title = styled(Textarea)`
z-index: 1;
line-height: 1.25;
margin-top: 1em;
margin-bottom: 0.5em;
margin-left: ${(props) => (props.$startsWithEmojiAndSpace ? "-1.2em" : 0)};
background: ${(props) => props.theme.background};
transition: ${(props) => props.theme.backgroundTransition};
color: ${(props) => props.theme.text};
Expand All @@ -162,6 +181,20 @@ const Title = styled(Textarea)`
color: ${(props) => props.theme.placeholder};
-webkit-text-fill-color: ${(props) => props.theme.placeholder};
}

${AnimatedStar} {
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
}

&:hover {
${AnimatedStar} {
opacity: 0.5;

&:hover {
opacity: 1;
}
}
}
`;

export default DocumentEditor;
4 changes: 3 additions & 1 deletion app/utils/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export const metaDisplay = isMac ? "⌘" : "Ctrl";

export const meta = isMac ? "cmd" : "ctrl";

export function isMetaKey(event: KeyboardEvent | MouseEvent) {
export function isMetaKey(
event: KeyboardEvent | MouseEvent | SyntheticKeyboardEvent<>
) {
return isMac ? event.metaKey : event.ctrlKey;
}