fix: Bag 'o fixes

Remove menu hover styles on mobile
Fixed duplicate hover+active behavior on editor menus
Fixed editor menus visibly scroll to the top when reopened
Fixed some minor editor spacing issues
Renamed shred routeHelpers -> urlHelpers
This commit is contained in:
Tom Moor
2022-01-25 23:43:11 -08:00
parent 13b8ed58fd
commit 175857753e
21 changed files with 103 additions and 64 deletions

View File

@@ -17,7 +17,7 @@ import {
changelogUrl,
mailToUrl,
githubIssuesUrl,
} from "@shared/utils/routeHelpers";
} from "@shared/utils/urlHelpers";
import stores from "~/stores";
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { createAction } from "~/actions";

View File

@@ -3,6 +3,7 @@ import * as React from "react";
import { MenuItem as BaseMenuItem } from "reakit/Menu";
import styled, { css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { hover } from "~/styles";
import MenuIconWrapper from "../MenuIconWrapper";
type Props = {
@@ -123,7 +124,7 @@ export const MenuAnchorCSS = css<{ level?: number; disabled?: boolean }>`
? "pointer-events: none;"
: `
&:hover,
&:${hover},
&:focus,
&.focus-visible {
color: ${props.theme.white};

View File

@@ -19,6 +19,7 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import DocumentMenu from "~/menus/DocumentMenu";
import { hover } from "~/styles";
import { newDocumentPath } from "~/utils/routeHelpers";
type Props = {
@@ -200,7 +201,7 @@ const DocumentLink = styled(Link)<{
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
}
&:hover,
&:${hover},
&:active,
&:focus,
&:focus-within {

View File

@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { withTranslation, Trans, WithTranslation } from "react-i18next";
import styled from "styled-components";
import { githubIssuesUrl } from "@shared/utils/routeHelpers";
import { githubIssuesUrl } from "@shared/utils/urlHelpers";
import Button from "~/components/Button";
import CenteredContent from "~/components/CenteredContent";
import HelpText from "~/components/HelpText";

View File

@@ -3,6 +3,7 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import styled, { useTheme } from "styled-components";
import Document from "~/models/Document";
import { hover } from "~/styles";
import NudeButton from "./NudeButton";
type Props = {
@@ -56,7 +57,7 @@ export const AnimatedStar = styled(StarredIcon)`
flex-shrink: 0;
transition: all 100ms ease-in-out;
&:hover {
&: ${hover} {
transform: scale(1.1);
}
&:active {

View File

@@ -10,13 +10,9 @@ type BlockMenuProps = Omit<
> &
Required<Pick<Props, "onLinkToolbarOpen" | "embeds">>;
class BlockMenu extends React.Component<BlockMenuProps> {
get items() {
return getMenuItems(this.props.dictionary);
}
clearSearch = () => {
const { state, dispatch } = this.props.view;
function BlockMenu(props: BlockMenuProps) {
const clearSearch = () => {
const { state, dispatch } = props.view;
const parent = findParentNode((node) => !!node)(state.selection);
if (parent) {
@@ -24,27 +20,25 @@ class BlockMenu extends React.Component<BlockMenuProps> {
}
};
render() {
return (
<CommandMenu
{...this.props}
filterable={true}
onClearSearch={this.clearSearch}
renderMenuItem={(item, _index, options) => {
return (
<BlockMenuItem
onClick={options.onClick}
selected={options.selected}
icon={item.icon}
title={item.title}
shortcut={item.shortcut}
/>
);
}}
items={this.items}
/>
);
}
return (
<CommandMenu
{...props}
filterable={true}
onClearSearch={clearSearch}
renderMenuItem={(item, _index, options) => {
return (
<BlockMenuItem
onClick={options.onClick}
selected={options.selected}
icon={item.icon}
title={item.title}
shortcut={item.shortcut}
/>
);
}}
items={getMenuItems(props.dictionary)}
/>
);
}
export default BlockMenu;

View File

@@ -29,7 +29,7 @@ function BlockMenuItem({
if (selected && node) {
scrollIntoView(node, {
scrollMode: "if-needed",
block: "center",
block: "nearest",
boundary: (parent) => {
// All the parent elements of your target are checked until they
// reach the #block-menu-container. Prevents body and other parent
@@ -64,6 +64,12 @@ function BlockMenuItem({
);
}
const Shortcut = styled.span`
color: ${(props) => props.theme.textTertiary};
flex-grow: 1;
text-align: right;
`;
const MenuItem = styled.button<{
selected: boolean;
}>`
@@ -90,7 +96,6 @@ const MenuItem = styled.button<{
padding: 0 16px;
outline: none;
&:hover,
&:active {
color: ${(props) => props.theme.blockToolbarTextSelected};
background: ${(props) =>
@@ -98,13 +103,11 @@ const MenuItem = styled.button<{
? props.theme.blockToolbarSelectedBackground ||
props.theme.blockToolbarTrigger
: props.theme.blockToolbarHoverBackground};
${Shortcut} {
color: ${(props) => props.theme.textSecondary};
}
}
`;
const Shortcut = styled.span`
color: ${(props) => props.theme.textSecondary};
flex-grow: 1;
text-align: right;
`;
export default BlockMenuItem;

View File

@@ -84,6 +84,11 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
componentDidUpdate(prevProps: Props) {
if (!prevProps.isActive && this.props.isActive) {
// reset scroll position to top when opening menu as the contents are
// hidden, not unrendered
if (this.menuRef.current) {
this.menuRef.current.scroll({ top: 0 });
}
const position = this.calculatePosition(this.props);
this.setState({
@@ -485,16 +490,25 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
</ListItem>
);
}
const selected = index === this.state.selectedIndex && isActive;
if (!item.title) {
return null;
}
const handlePointer = () => {
if (this.state.selectedIndex !== index) {
this.setState({ selectedIndex: index });
}
};
return (
<ListItem key={index}>
<ListItem
key={index}
onPointerMove={handlePointer}
onPointerDown={handlePointer}
>
{this.props.renderMenuItem(item as any, index, {
selected,
selected: index === this.state.selectedIndex,
onClick: () => this.insertItem(item),
})}
</ListItem>

View File

@@ -540,7 +540,8 @@ const EditorStyles = styled.div<{
ul.checkbox_list {
list-style: none;
padding: 0;
margin: ${(props) => (props.rtl ? "0 -24px 0 0" : "0 0 0 -24px")};
margin-left: ${(props) => (props.rtl ? "0" : "-24px")};
margin-right: ${(props) => (props.rtl ? "-24px" : "0")};
}
ul li,

View File

@@ -18,10 +18,7 @@ import {
} from "outline-icons";
import { MenuItem } from "@shared/editor/types";
import { Dictionary } from "~/hooks/useDictionary";
const SSR = typeof window === "undefined";
const isMac = !SSR && window.navigator.platform === "MacIntel";
const mod = isMac ? "⌘" : "ctrl";
import { metaDisplay } from "~/utils/keyboard";
export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
return [
@@ -84,7 +81,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
name: "blockquote",
title: dictionary.quote,
icon: BlockQuoteIcon,
shortcut: `${mod} ]`,
shortcut: `${metaDisplay} ]`,
},
{
name: "code_block",
@@ -97,7 +94,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
name: "hr",
title: dictionary.hr,
icon: HorizontalRuleIcon,
shortcut: `${mod} _`,
shortcut: `${metaDisplay} _`,
keywords: "horizontal rule break line",
},
{
@@ -117,7 +114,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
name: "link",
title: dictionary.link,
icon: LinkIcon,
shortcut: `${mod} k`,
shortcut: `${metaDisplay} k`,
keywords: "link url uri href",
},
{

View File

@@ -6,6 +6,7 @@ import styled from "styled-components";
import Document from "~/models/Document";
import DocumentMeta from "~/components/DocumentMeta";
import Flex from "~/components/Flex";
import { hover } from "~/styles";
import { NavigationNode } from "~/types";
type Props = {
@@ -25,7 +26,7 @@ const DocumentLink = styled(Link)`
overflow: hidden;
position: relative;
&:hover,
&:${hover},
&:active,
&:focus {
background: ${(props) => props.theme.listItemHoverBackground};

View File

@@ -8,6 +8,7 @@ import Fade from "~/components/Fade";
import NudeButton from "~/components/NudeButton";
import Tooltip from "~/components/Tooltip";
import useStores from "~/hooks/useStores";
import { hover } from "~/styles";
import { searchUrl } from "~/utils/routeHelpers";
function RecentSearches() {
@@ -90,7 +91,7 @@ const RecentSearch = styled(Link)`
padding: 1px 4px;
border-radius: 4px;
&:hover {
&: ${hover} {
color: ${(props) => props.theme.text};
background: ${(props) => props.theme.secondaryBackground};

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { slackAuth } from "@shared/utils/routeHelpers";
import { slackAuth } from "@shared/utils/urlHelpers";
import Button from "~/components/Button";
import env from "~/env";

8
app/styles/index.ts Normal file
View File

@@ -0,0 +1,8 @@
import { isTouchDevice } from "~/utils/browser";
/**
* Returns "hover" on a non-touch device and "active" on a touch device. To
* avoid "sticky" hover on mobile. Use `&:${hover} {...}` instead of
* using `&:hover {...}`.
*/
export const hover = isTouchDevice() ? "active" : "hover";

17
app/utils/browser.ts Normal file
View File

@@ -0,0 +1,17 @@
/**
* Returns true if the client is a touch device.
*/
export function isTouchDevice(): boolean {
if (typeof window === "undefined") {
return false;
}
return window.matchMedia?.("(hover: none) and (pointer: coarse)")?.matches;
}
/**
* Returns true if the client is running on a Mac.
*/
export function isMac(): boolean {
const SSR = typeof window === "undefined";
return !SSR && window.navigator.platform === "MacIntel";
}

View File

@@ -1,11 +1,11 @@
const isMac = window.navigator.platform === "MacIntel";
import { isMac } from "~/utils/browser";
export const metaDisplay = isMac ? "⌘" : "Ctrl";
export const metaDisplay = isMac() ? "⌘" : "Ctrl";
export const meta = isMac ? "cmd" : "ctrl";
export const meta = isMac() ? "cmd" : "ctrl";
export function isModKey(
event: KeyboardEvent | MouseEvent | React.KeyboardEvent
) {
return isMac ? event.metaKey : event.ctrlKey;
return isMac() ? event.metaKey : event.ctrlKey;
}

View File

@@ -1,7 +1,7 @@
import { Table, TBody, TR, TD } from "oy-vey";
import * as React from "react";
import theme from "@shared/theme";
import { twitterUrl } from "@shared/utils/routeHelpers";
import { twitterUrl } from "@shared/utils/urlHelpers";
type Props = {
unsubscribeUrl?: string;

View File

@@ -27,7 +27,7 @@ import {
} from "sequelize-typescript";
import isUUID from "validator/lib/isUUID";
import { sortNavigationNodes } from "@shared/utils/collections";
import { SLUG_URL_REGEX } from "@shared/utils/routeHelpers";
import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers";
import slugify from "@server/utils/slugify";
import { NavigationNode, CollectionSort } from "~/types";
import CollectionGroup from "./CollectionGroup";

View File

@@ -32,8 +32,8 @@ import { MAX_TITLE_LENGTH } from "@shared/constants";
import { DateFilter } from "@shared/types";
import getTasks from "@shared/utils/getTasks";
import parseTitle from "@shared/utils/parseTitle";
import { SLUG_URL_REGEX } from "@shared/utils/routeHelpers";
import unescape from "@shared/utils/unescape";
import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers";
import slugify from "@server/utils/slugify";
import Backlink from "./Backlink";
import Collection from "./Collection";

View File

@@ -1,5 +1,5 @@
import Router from "koa-router";
import { signin } from "@shared/utils/routeHelpers";
import { signin } from "@shared/utils/urlHelpers";
import { requireDirectory } from "@server/utils/fs";
interface AuthenicationProvider {

View File

@@ -32,7 +32,7 @@ export function githubIssuesUrl(): string {
}
export function twitterUrl(): string {
return "https://twitter.com/outlinewiki";
return "https://twitter.com/getoutline";
}
export function mailToUrl(): string {