feat: remove overflowed tags feature

This commit is contained in:
Antonella Sgarlatta
2021-06-02 19:50:38 -03:00
parent ba1f151d46
commit 02f3c7c26c
4 changed files with 8 additions and 190 deletions

View File

@@ -1,7 +1,7 @@
import { WebApplication } from '@/ui_models/application';
import { SNTag } from '@standardnotes/snjs';
import { FunctionalComponent } from 'preact';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'preact/hooks';
import { Icon } from './Icon';
import { Disclosure, DisclosurePanel } from '@reach/disclosure';
import { useCloseOnBlur } from './utils';
@@ -16,7 +16,7 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
application,
appState,
}) => {
const { tagElements, tags, tagsContainerMaxWidth, tagsOverflowed } = appState.activeNote;
const { tagElements } = appState.activeNote;
const [searchQuery, setSearchQuery] = useState('');
const [dropdownVisible, setDropdownVisible] = useState(false);
@@ -82,19 +82,6 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
await createAndAddNewTag();
};
const reloadInputOverflowed = useCallback(() => {
const overflowed = !tagsOverflowed && appState.activeNote.isElementOverflowed(inputRef.current);
appState.activeNote.setInputOverflowed(overflowed);
}, [appState.activeNote, tagsOverflowed]);
useEffect(() => {
reloadInputOverflowed();
}, [
reloadInputOverflowed,
tagsContainerMaxWidth,
tags,
]);
useEffect(() => {
setHintVisible(
searchQuery !== '' && !tagResults.some((tag) => tag.title === searchQuery)
@@ -111,7 +98,6 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
onChange={onSearchQueryChange}
type="text"
placeholder="Add tag"
tabIndex={tagsOverflowed ? -1 : 0}
onBlur={closeOnBlur}
onFocus={showDropdown}
onKeyUp={(event) => {
@@ -139,7 +125,6 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
className="sn-dropdown-item"
onClick={() => onTagOptionClick(tag)}
onBlur={closeOnBlur}
tabIndex={tagsOverflowed ? -1 : 0}
>
<Icon type="hashtag" className="color-neutral mr-2 min-h-5 min-w-5" />
<span className="whitespace-nowrap overflow-hidden overflow-ellipsis">
@@ -177,7 +162,6 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
className="sn-dropdown-item"
onClick={onTagHintClick}
onBlur={closeOnBlur}
tabIndex={tagsOverflowed ? -1 : 0}
>
<span>Create new tag:</span>
<span className="bg-contrast rounded text-xs color-text py-1 pl-1 pr-2 flex items-center ml-2">

View File

@@ -1,6 +1,6 @@
import { Icon } from './Icon';
import { FunctionalComponent } from 'preact';
import { useCallback, useRef, useState } from 'preact/hooks';
import { useRef, useState } from 'preact/hooks';
import { AppState } from '@/ui_models/app_state';
import { SNTag } from '@standardnotes/snjs/dist/@types';
import { useEffect } from 'react';
@@ -13,12 +13,9 @@ type Props = {
export const NoteTag: FunctionalComponent<Props> = ({ appState, tag }) => {
const {
tags,
tagsContainerExpanded,
tagsContainerMaxWidth,
} = appState.activeNote;
const [overflowed, setOverflowed] = useState(false);
const [contextMenuOpen, setContextMenuOpen] = useState(false);
const [contextMenuPosition, setContextMenuPosition] = useState({ top: 0, left: 0 });
@@ -36,15 +33,6 @@ export const NoteTag: FunctionalComponent<Props> = ({ appState, tag }) => {
appState.setSelectedTag(tag);
};
const reloadOverflowed = useCallback(() => {
const overflowed = appState.activeNote.isTagOverflowed(tag);
setOverflowed(overflowed);
}, [appState.activeNote, tag]);
useEffect(() => {
reloadOverflowed();
}, [reloadOverflowed, tags, tagsContainerExpanded, tagsContainerMaxWidth]);
const contextMenuListener = (event: MouseEvent) => {
event.preventDefault();
setContextMenuPosition({
@@ -78,7 +66,6 @@ export const NoteTag: FunctionalComponent<Props> = ({ appState, tag }) => {
deleteTag();
}
}}
tabIndex={overflowed ? -1 : 0}
onBlur={closeOnBlur}
>
<Icon type="hashtag" className="sn-icon--small color-neutral mr-1" />

View File

@@ -1,10 +1,10 @@
import { AppState } from '@/ui_models/app_state';
import { observer } from 'mobx-react-lite';
import { toDirective, useCloseOnClickOutside } from './utils';
import { toDirective } from './utils';
import { AutocompleteTagInput } from './AutocompleteTagInput';
import { WebApplication } from '@/ui_models/application';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { NoteTag } from './NoteTag';
import { useEffect } from 'preact/hooks';
type Props = {
application: WebApplication;
@@ -13,72 +13,17 @@ type Props = {
const NoteTagsContainer = observer(({ application, appState }: Props) => {
const {
inputOverflowed,
overflowCountPosition,
overflowedTagsCount,
tagElements,
tags,
tagsContainerMaxWidth,
tagsContainerExpanded,
tagsOverflowed,
} = appState.activeNote;
const [expandedContainerHeight, setExpandedContainerHeight] = useState(0);
const tagsContainerRef = useRef<HTMLDivElement>();
const overflowButtonRef = useRef<HTMLButtonElement>();
useCloseOnClickOutside(tagsContainerRef, (expanded: boolean) => {
if (tagsContainerExpanded) {
appState.activeNote.setTagsContainerExpanded(expanded);
}
});
const reloadExpandedContainerHeight = useCallback(() => {
setExpandedContainerHeight(tagsContainerRef.current.scrollHeight);
}, []);
useEffect(() => {
appState.activeNote.reloadTagsContainerLayout();
reloadExpandedContainerHeight();
}, [
appState.activeNote,
reloadExpandedContainerHeight,
tags,
tagsContainerMaxWidth,
]);
useEffect(() => {
let tagResizeObserver: ResizeObserver;
if (ResizeObserver) {
tagResizeObserver = new ResizeObserver(() => {
appState.activeNote.reloadTagsContainerLayout();
reloadExpandedContainerHeight();
});
tagElements.forEach(
(tagElement) => tagElement && tagResizeObserver.observe(tagElement)
);
}
return () => {
if (tagResizeObserver) {
tagResizeObserver.disconnect();
}
};
}, [appState.activeNote, reloadExpandedContainerHeight, tagElements]);
appState.activeNote.reloadTagsContainerMaxWidth();
}, [appState.activeNote]);
return (
<div
className={`flex transition-height duration-150 relative ${
inputOverflowed ? 'h-18' : 'h-9'
}`}
style={tagsContainerExpanded ? { height: expandedContainerHeight } : {}}
>
<div
ref={tagsContainerRef}
className={`absolute bg-default flex flex-wrap pl-1 -ml-1 ${
inputOverflowed ? 'h-18' : 'h-9'
} ${tagsContainerExpanded || !tagsOverflowed ? '' : 'overflow-hidden'}`}
className="bg-default flex flex-wrap pl-1 -ml-1"
style={{
maxWidth: tagsContainerMaxWidth,
}}
@@ -92,18 +37,6 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
))}
<AutocompleteTagInput application={application} appState={appState} />
</div>
{tagsOverflowed && (
<button
ref={overflowButtonRef}
type="button"
className="sn-tag ml-1 absolute"
onClick={() => appState.activeNote.setTagsContainerExpanded(true)}
style={{ left: overflowCountPosition }}
>
+{overflowedTagsCount}
</button>
)}
</div>
);
});

View File

@@ -5,7 +5,6 @@ import {
} from '@standardnotes/snjs';
import {
action,
computed,
makeObservable,
observable,
} from 'mobx';
@@ -13,14 +12,9 @@ import { WebApplication } from '../application';
import { AppState } from './app_state';
export class ActiveNoteState {
inputOverflowed = false;
overflowCountPosition = 0;
overflowedTagsCount = 0;
tagElements: (HTMLButtonElement | undefined)[] = [];
tagFocused = false;
tags: SNTag[] = [];
tagsContainerMaxWidth: number | 'auto' = 0;
tagsContainerExpanded = false;
constructor(
private application: WebApplication,
@@ -28,24 +22,12 @@ export class ActiveNoteState {
appEventListeners: (() => void)[]
) {
makeObservable(this, {
inputOverflowed: observable,
overflowCountPosition: observable,
overflowedTagsCount: observable,
tagElements: observable,
tagFocused: observable,
tags: observable,
tagsContainerExpanded: observable,
tagsContainerMaxWidth: observable,
tagsOverflowed: computed,
setInputOverflowed: action,
setOverflowCountPosition: action,
setOverflowedTagsCount: action,
setTagElement: action,
setTagFocused: action,
setTags: action,
setTagsContainerExpanded: action,
setTagsContainerMaxWidth: action,
reloadTags: action,
});
@@ -60,23 +42,6 @@ export class ActiveNoteState {
get activeNote(): SNNote | undefined {
return this.appState.notes.activeEditor?.note;
}
get tagsOverflowed(): boolean {
return this.overflowedTagsCount > 0 && !this.tagsContainerExpanded;
}
setInputOverflowed(overflowed: boolean): void {
this.inputOverflowed = overflowed;
}
setOverflowCountPosition(position: number): void {
this.overflowCountPosition = position;
}
setOverflowedTagsCount(count: number): void {
this.overflowedTagsCount = count;
}
setTagElement(tag: SNTag, element: HTMLButtonElement): void {
const tagIndex = this.getTagIndex(tag);
if (tagIndex > -1) {
@@ -84,10 +49,6 @@ export class ActiveNoteState {
}
}
setTagFocused(focused: boolean): void {
this.tagFocused = focused;
}
setTagElements(elements: (HTMLButtonElement | undefined)[]): void {
this.tagElements = elements;
}
@@ -96,10 +57,6 @@ export class ActiveNoteState {
this.tags = tags;
}
setTagsContainerExpanded(expanded: boolean): void {
this.tagsContainerExpanded = expanded;
}
setTagsContainerMaxWidth(width: number): void {
this.tagsContainerMaxWidth = width;
}
@@ -122,25 +79,6 @@ export class ActiveNoteState {
}
}
isElementOverflowed(element: HTMLElement): boolean {
if (
this.tagElements.length === 0 ||
!this.tagElements[0]
) {
return false;
}
const firstTagTop = this.tagElements[0].offsetTop;
return element.offsetTop > firstTagTop;
}
isTagOverflowed(tag: SNTag): boolean {
if (this.tagsContainerExpanded) {
return false;
}
const tagElement = this.getTagElement(tag);
return tagElement ? this.isElementOverflowed(tagElement) : false;
}
reloadTags(): void {
const { activeNote } = this;
if (activeNote) {
@@ -151,24 +89,6 @@ export class ActiveNoteState {
}
}
reloadOverflowCountPosition(): void {
const lastVisibleTagIndex = this.tagElements.findIndex(tagElement => tagElement && this.isElementOverflowed(tagElement)) - 1;
if (lastVisibleTagIndex > -1 && this.tagElements.length > lastVisibleTagIndex) {
const lastVisibleTagElement = this.tagElements[lastVisibleTagIndex];
if (lastVisibleTagElement) {
const { offsetLeft, offsetWidth } = lastVisibleTagElement;
this.setOverflowCountPosition(offsetLeft + offsetWidth);
}
}
}
reloadOverflowedTagsCount(): void {
const count = this.tagElements.filter((tagElement) =>
tagElement && this.isElementOverflowed(tagElement)
).length;
this.setOverflowedTagsCount(count);
}
reloadTagsContainerMaxWidth(): void {
const EDITOR_ELEMENT_ID = 'editor-column';
const defaultFontSize = parseFloat(window.getComputedStyle(
@@ -183,12 +103,6 @@ export class ActiveNoteState {
}
}
reloadTagsContainerLayout(): void {
this.reloadTagsContainerMaxWidth();
this.reloadOverflowedTagsCount();
this.reloadOverflowCountPosition();
}
async addTagToActiveNote(tag: SNTag): Promise<void> {
const { activeNote } = this;
if (activeNote) {