Skip to content

Commit

Permalink
Document features normalization (#4638)
Browse files Browse the repository at this point in the history
* Document features normalization

* remove unused dep

* disable debugger

* Add back dep

* Fix a thing

* Revert a thing

* revert a change

* Fix a code block bug

* refactoring

* linting

* WIP

* More WIP

* Perf things

* Perf things

* Fix a bug

* First pass of insert menu

* Fix a thing

* Fix some things

* Nearly finish insertMenu

* Fix a thing

* Update packages-next/fields-document/src/DocumentEditor/index.tsx

* Fix a bug

* Remove some unnecessary code

* Remove a dep in a deps array

* Remove some comments

* Add a comment

* Fix a thing

* Refactor a thing

* Some refactoring

* Fix a bug

* Fix another thing

Co-authored-by: Jed Watson <jed.watson@me.com>
  • Loading branch information
emmatown and JedWatson committed Jan 11, 2021
1 parent da1cad3 commit 160bd77
Show file tree
Hide file tree
Showing 37 changed files with 2,661 additions and 624 deletions.
6 changes: 6 additions & 0 deletions .changeset/strong-planes-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystone-ui/popover': patch
'@keystone-ui/tooltip': patch
---

Improve performance when scrolling and resizing the page
1 change: 1 addition & 0 deletions design-system/packages/popover/src/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const useControlledPopover = (
modifiers: [
...(popperOptions.modifiers || []),
{ name: 'arrow', options: { element: arrowElement } },
{ name: 'eventListeners', options: { scroll: isOpen, resize: isOpen } },
],
});

Expand Down
26 changes: 16 additions & 10 deletions design-system/packages/tooltip/src/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
useEffect,
useRef,
memo,
useCallback,
useMemo,
} from 'react';
import { applyRefs } from 'apply-ref';
import { jsx, useId, useTheme, Portal } from '@keystone-ui/core';
Expand Down Expand Up @@ -61,8 +63,8 @@ export const Tooltip = ({
});

const tooltipId = useId();
const showTooltip = () => setOpen(true);
const hideTooltip = () => setOpen(false);
const showTooltip = useCallback(() => setOpen(true), []);
const hideTooltip = useCallback(() => setOpen(false), []);
const internalRef = useRef<HTMLElement>(null);

// avoid overriding the consumer's `onClick` handler
Expand All @@ -78,14 +80,18 @@ export const Tooltip = ({

return (
<Fragment>
{children({
onMouseEnter: showTooltip,
onMouseLeave: hideTooltip,
onFocus: showTooltip,
onBlur: hideTooltip,
'aria-describedby': tooltipId,
ref: applyRefs(trigger.ref, internalRef),
})}
{useMemo(
() =>
children({
onMouseEnter: showTooltip,
onMouseLeave: hideTooltip,
onFocus: showTooltip,
onBlur: hideTooltip,
'aria-describedby': tooltipId,
ref: applyRefs(trigger.ref, internalRef),
}),
[children, showTooltip, hideTooltip, tooltipId, trigger.ref, internalRef]
)}
<TooltipElement
id={tooltipId}
isVisible={isOpen}
Expand Down
20 changes: 16 additions & 4 deletions examples-next/basic/admin/fieldViews/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ export const componentBlocks = {
<h1>{props.title}</h1>
<NotEditable>
<ul>
{props.authors.value.map(author => {
{props.authors.value.map((author, i) => {
return (
<li>
<li key={i}>
{author.label}
<ul>
{author.data.posts.map((post: { title: string | null }) => {
Expand Down Expand Up @@ -216,7 +216,14 @@ export const componentBlocks = {
] as const,
defaultValue: 'info',
}),
content: fields.child({ kind: 'block', placeholder: '' }),
content: fields.child({
kind: 'block',
placeholder: '',
formatting: 'inherit',
dividers: 'inherit',
links: 'inherit',
relationships: 'inherit',
}),
},
toolbar({ props, onRemove }) {
return (
Expand Down Expand Up @@ -280,7 +287,12 @@ export const componentBlocks = {
},
label: 'Quote',
props: {
content: fields.child({ kind: 'block', placeholder: 'Quote...' }),
content: fields.child({
kind: 'block',
placeholder: 'Quote...',
formatting: { inlineMarks: 'inherit', softBreaks: 'inherit' },
links: 'inherit',
}),
attribution: fields.child({ kind: 'inline', placeholder: 'Attribution...' }),
},
chromeless: true,
Expand Down
2 changes: 2 additions & 0 deletions packages-next/fields-document/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@emotion/weak-memoize": "^0.2.5",
"@keystone-next/admin-ui": "^7.0.1",
"@keystone-next/admin-ui-utils": "^2.0.6",
"@keystone-next/fields": "^4.0.3",
Expand All @@ -35,6 +36,7 @@
"io-ts": "^2.2.13",
"is-hotkey": "^0.2.0",
"is-url": "^1.2.4",
"match-sorter": "^6.1.0",
"react": "^16.14.0",
"slate": "npm:slate@^0.59.0",
"slate-history": "^0.59.0",
Expand Down
71 changes: 31 additions & 40 deletions packages-next/fields-document/src/DocumentEditor/Toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/** @jsx jsx */

import { Fragment, ReactNode, forwardRef, useState, memo, HTMLAttributes, useMemo } from 'react';
import { Editor, Transforms } from 'slate';
import { useEditor, useSlate } from 'slate-react';
import { Transforms } from 'slate';
import { applyRefs } from 'apply-ref';

import { jsx, useTheme } from '@keystone-ui/core';
Expand All @@ -20,7 +19,7 @@ import { MoreHorizontalIcon } from '@keystone-ui/icons/icons/MoreHorizontalIcon'
import { InlineDialog, ToolbarButton, ToolbarGroup, ToolbarSeparator } from './primitives';
import { linkButton } from './link';
import { BlockComponentsButtons } from './component-blocks';
import { Mark, isMarkActive, toggleMark } from './utils';
import { Mark, toggleMark } from './utils';
import { LayoutsButton } from './layouts';
import { ListButton } from './lists';
import { blockquoteButton } from './blockquote';
Expand All @@ -29,10 +28,11 @@ import { DocumentFeatures } from '../views';
import { insertCodeBlock } from './code-block';
import { TextAlignMenu } from './alignment';
import { dividerButton } from './divider';
import { useToolbarState } from './toolbar-state';

// TODO: how to manage separators with dynamic feature sets...

export const Toolbar = memo(function Toolbar({
export function Toolbar({
documentFeatures,
viewState,
}: {
Expand Down Expand Up @@ -104,30 +104,35 @@ export const Toolbar = memo(function Toolbar({
)}
</ToolbarContainer>
);
});
}

/* UI Components */

const MarkButton = forwardRef<any, { children: ReactNode; type: Mark }>(function MarkButton(
props,
ref
) {
const editor = useSlate();
const isActive = isMarkActive(editor, props.type);
const {
editor,
marks: {
[props.type]: { isDisabled, isSelected },
},
} = useToolbarState();
return useMemo(() => {
const { type, ...restProps } = props;
return (
<ToolbarButton
ref={ref}
isSelected={isActive}
isDisabled={isDisabled}
isSelected={isSelected}
onMouseDown={event => {
event.preventDefault();
toggleMark(editor, type);
}}
{...restProps}
/>
);
}, [editor, isActive, props]);
}, [editor, isDisabled, isSelected, props]);
});

const ToolbarContainer = ({ children }: { children: ReactNode }) => {
Expand Down Expand Up @@ -161,20 +166,16 @@ function HeadingButton({
showMenu: boolean;
onToggleShowMenu: () => void;
}) {
const editor = useSlate();
// prep button label
let [headingNodes] = Editor.nodes(editor, {
match: n => n.type === 'heading',
});
let buttonLabel = 'Normal text';
if (headingNodes) {
buttonLabel = 'Heading ' + headingNodes[0].level;
}
const { textStyles } = useToolbarState();
let buttonLabel =
textStyles.selected === 'normal' ? 'Normal text' : 'Heading ' + textStyles.selected;
const isDisabled = textStyles.allowedHeadingLevels.length === 0;
return useMemo(
() => (
<ToolbarButton
ref={trigger.ref}
isPressed={showMenu}
isDisabled={isDisabled}
onClick={event => {
event.preventDefault();
onToggleShowMenu();
Expand All @@ -186,7 +187,7 @@ function HeadingButton({
{downIcon}
</ToolbarButton>
),
[buttonLabel, trigger, showMenu, onToggleShowMenu]
[buttonLabel, trigger, showMenu, onToggleShowMenu, isDisabled]
);
}

Expand Down Expand Up @@ -250,31 +251,23 @@ function HeadingDialog({
headingLevels: DocumentFeatures['formatting']['headingLevels'];
onCloseMenu: () => void;
}) {
const editor = useSlate();
const { editor, textStyles } = useToolbarState();
return (
<ToolbarGroup direction="column">
{headingLevels.map(hNum => {
let [node] = Editor.nodes(editor, {
match: n => n.type === 'heading' && n.level === hNum,
});
let isActive = !!node;
let Tag = `h${hNum}` as 'h1';

const isSelected = textStyles.selected === hNum;
return (
<ToolbarButton
isSelected={isActive}
key={hNum}
isSelected={isSelected}
onMouseDown={event => {
event.preventDefault();
Transforms.setNodes(
editor,
isActive
? {
type: 'paragraph',
level: undefined,
}
: { type: 'heading', level: hNum }
);

if (isSelected) {
Transforms.unwrapNodes(editor, { match: n => n.type === 'heading' });
} else {
Transforms.wrapNodes(editor, { type: 'heading', level: hNum, children: [] });
}
onCloseMenu();
}}
>
Expand Down Expand Up @@ -350,15 +343,13 @@ function InnerInsertBlockMenu({
blockTypes: DocumentFeatures['formatting']['blockTypes'];
onClose: () => void;
}) {
// useEditor does not update when the value/selection changes.
// that's fine for what it's being used for here
// because we're just inserting things on events, not reading things in render
const editor = useEditor();
const { editor, code } = useToolbarState();

return (
<ToolbarGroup direction="column">
{blockTypes.code && (
<ToolbarButton
isDisabled={code.isDisabled}
onMouseDown={event => {
event.preventDefault();
insertCodeBlock(editor);
Expand Down
34 changes: 13 additions & 21 deletions packages-next/fields-document/src/DocumentEditor/alignment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { useControlledPopover } from '@keystone-ui/popover';
import { Tooltip } from '@keystone-ui/tooltip';
import { applyRefs } from 'apply-ref';
import { useState, ComponentProps, useMemo } from 'react';
import { Editor, Transforms } from 'slate';
import { useSlate } from 'slate-react';
import { Transforms } from 'slate';
import { DocumentFeatures } from '../views';
import { InlineDialog, ToolbarButton, ToolbarGroup } from './primitives';
import { useToolbarState } from './toolbar-state';

export const TextAlignMenu = ({
alignment,
Expand Down Expand Up @@ -77,7 +77,10 @@ function TextAlignDialog({
alignment: DocumentFeatures['formatting']['alignment'];
onClose: () => void;
}) {
const { currentTextAlign, editor } = useTextAlignInfo();
const {
alignment: { selected },
editor,
} = useToolbarState();
const alignments = [
'start',
...(Object.keys(alignment) as (keyof typeof alignment)[]).filter(key => alignment[key]),
Expand All @@ -88,7 +91,7 @@ function TextAlignDialog({
<Tooltip key={alignment} content={`Align ${alignment}`} weight="subtle">
{attrs => (
<ToolbarButton
isSelected={currentTextAlign === alignment}
isSelected={selected === alignment}
onMouseDown={event => {
event.preventDefault();
if (alignment === 'start') {
Expand Down Expand Up @@ -123,30 +126,19 @@ const alignmentIcons = {
end: <AlignRightIcon size="small" />,
};

function useTextAlignInfo() {
const editor = useSlate();
const [firstAlignableNode] = Editor.nodes(editor, {
match: node => node.type === 'paragraph' || node.type === 'heading',
});
const alignmentAllowed = !!firstAlignableNode;
const currentTextAlign: 'start' | 'center' | 'end' =
((firstAlignableNode && firstAlignableNode[0] && firstAlignableNode[0].textAlign) as any) ||
'start';

return { alignmentAllowed, currentTextAlign, editor };
}

function TextAlignButton(props: {
onToggle: () => void;
trigger: ReturnType<typeof useControlledPopover>['trigger'];
showMenu: boolean;
attrs: Parameters<ComponentProps<typeof Tooltip>['children']>[0];
}) {
const { alignmentAllowed, currentTextAlign } = useTextAlignInfo();
const {
alignment: { isDisabled, selected },
} = useToolbarState();
return useMemo(
() => (
<ToolbarButton
isDisabled={!alignmentAllowed}
isDisabled={isDisabled}
isPressed={props.showMenu}
onClick={event => {
event.preventDefault();
Expand All @@ -156,11 +148,11 @@ function TextAlignButton(props: {
{...props.trigger.props}
ref={applyRefs(props.attrs.ref, props.trigger.ref)}
>
{alignmentIcons[currentTextAlign]}
{alignmentIcons[selected]}
{downIcon}
</ToolbarButton>
),
[alignmentAllowed, currentTextAlign, props]
[isDisabled, selected, props]
);
}

Expand Down
Loading

0 comments on commit 160bd77

Please sign in to comment.