fix: make sure input is visible when no tags are overflowed

This commit is contained in:
Antonella Sgarlatta
2021-06-02 12:20:39 -03:00
parent 53a55d4bc7
commit 3db87099e0
4 changed files with 67 additions and 35 deletions

View File

@@ -1,7 +1,7 @@
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { SNTag } from '@standardnotes/snjs'; import { SNTag } from '@standardnotes/snjs';
import { FunctionalComponent, RefObject } from 'preact'; import { FunctionalComponent, RefObject } from 'preact';
import { useEffect, useRef, useState } from 'preact/hooks'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { Disclosure, DisclosurePanel } from '@reach/disclosure'; import { Disclosure, DisclosurePanel } from '@reach/disclosure';
import { useCloseOnBlur } from './utils'; import { useCloseOnBlur } from './utils';
@@ -11,15 +11,15 @@ type Props = {
application: WebApplication; application: WebApplication;
appState: AppState; appState: AppState;
tagsRef: RefObject<HTMLButtonElement[]>; tagsRef: RefObject<HTMLButtonElement[]>;
tabIndex: number;
}; };
export const AutocompleteTagInput: FunctionalComponent<Props> = ({ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
application, application,
appState, appState,
tagsRef, tagsRef,
tabIndex,
}) => { }) => {
const { tags, tagsContainerMaxWidth, tagsOverflowed } = appState.activeNote;
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [dropdownVisible, setDropdownVisible] = useState(false); const [dropdownVisible, setDropdownVisible] = useState(false);
const [dropdownMaxHeight, setDropdownMaxHeight] = const [dropdownMaxHeight, setDropdownMaxHeight] =
@@ -84,6 +84,23 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
await createAndAddNewTag(); await createAndAddNewTag();
}; };
const reloadInputOverflowed = useCallback(() => {
let overflowed = false;
if (!tagsOverflowed && tagsRef.current && tagsRef.current.length > 0) {
const firstTagTop = tagsRef.current[0].offsetTop;
overflowed = inputRef.current.offsetTop > firstTagTop;
}
appState.activeNote.setInputOverflowed(overflowed);
}, [appState.activeNote, tagsOverflowed, tagsRef]);
useEffect(() => {
reloadInputOverflowed();
}, [
reloadInputOverflowed,
tagsContainerMaxWidth,
tags,
]);
useEffect(() => { useEffect(() => {
setHintVisible( setHintVisible(
searchQuery !== '' && !tagResults.some((tag) => tag.title === searchQuery) searchQuery !== '' && !tagResults.some((tag) => tag.title === searchQuery)
@@ -100,7 +117,7 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
onChange={onSearchQueryChange} onChange={onSearchQueryChange}
type="text" type="text"
placeholder="Add tag" placeholder="Add tag"
tabIndex={tabIndex} tabIndex={tagsOverflowed ? -1 : 0}
onBlur={closeOnBlur} onBlur={closeOnBlur}
onFocus={showDropdown} onFocus={showDropdown}
onKeyUp={(event) => { onKeyUp={(event) => {
@@ -129,7 +146,7 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={() => onTagOptionClick(tag)} onClick={() => onTagOptionClick(tag)}
onBlur={closeOnBlur} onBlur={closeOnBlur}
tabIndex={tabIndex} tabIndex={tagsOverflowed ? -1 : 0}
> >
<Icon type="hashtag" className="color-neutral mr-2 min-h-5 min-w-5" /> <Icon type="hashtag" className="color-neutral mr-2 min-h-5 min-w-5" />
<span className="whitespace-nowrap overflow-hidden overflow-ellipsis"> <span className="whitespace-nowrap overflow-hidden overflow-ellipsis">
@@ -167,7 +184,7 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={onTagHintClick} onClick={onTagHintClick}
onBlur={closeOnBlur} onBlur={closeOnBlur}
tabIndex={tabIndex} tabIndex={tagsOverflowed ? -1 : 0}
> >
<span>Create new tag:</span> <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"> <span className="bg-contrast rounded text-xs color-text py-1 pl-1 pr-2 flex items-center ml-2">

View File

@@ -14,6 +14,7 @@ type Props = {
const NoteTagsContainer = observer(({ application, appState }: Props) => { const NoteTagsContainer = observer(({ application, appState }: Props) => {
const { const {
inputOverflowed,
overflowedTagsCount, overflowedTagsCount,
tags, tags,
tagsContainerMaxWidth, tagsContainerMaxWidth,
@@ -82,8 +83,10 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
return; return;
} }
if (tagsRef.current[lastVisibleTagIndex]) { if (tagsRef.current[lastVisibleTagIndex]) {
const { offsetLeft: lastVisibleTagLeft, clientWidth: lastVisibleTagWidth } = const {
tagsRef.current[lastVisibleTagIndex]; offsetLeft: lastVisibleTagLeft,
clientWidth: lastVisibleTagWidth,
} = tagsRef.current[lastVisibleTagIndex];
setOverflowCountPosition(lastVisibleTagLeft + lastVisibleTagWidth); setOverflowCountPosition(lastVisibleTagLeft + lastVisibleTagWidth);
} }
}, [lastVisibleTagIndex, tagsContainerExpanded]); }, [lastVisibleTagIndex, tagsContainerExpanded]);
@@ -108,11 +111,7 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
useEffect(() => { useEffect(() => {
reloadTagsContainerLayout(); reloadTagsContainerLayout();
}, [ }, [reloadTagsContainerLayout, tags, tagsContainerMaxWidth]);
reloadTagsContainerLayout,
tags,
tagsContainerMaxWidth,
]);
useEffect(() => { useEffect(() => {
let tagResizeObserver: ResizeObserver; let tagResizeObserver: ResizeObserver;
@@ -120,7 +119,9 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
tagResizeObserver = new ResizeObserver(() => { tagResizeObserver = new ResizeObserver(() => {
reloadTagsContainerLayout(); reloadTagsContainerLayout();
}); });
tagsRef.current.forEach((tagElement) => tagResizeObserver.observe(tagElement)); tagsRef.current.forEach((tagElement) =>
tagResizeObserver.observe(tagElement)
);
} }
return () => { return () => {
@@ -132,15 +133,17 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
return ( return (
<div <div
className="flex transition-height duration-150 h-9 relative" className={`flex transition-height duration-150 relative ${
inputOverflowed ? 'h-18' : 'h-9'
}`}
ref={containerRef} ref={containerRef}
style={tagsContainerExpanded ? { height: expandedContainerHeight } : {}} style={tagsContainerExpanded ? { height: expandedContainerHeight } : {}}
> >
<div <div
ref={tagsContainerRef} ref={tagsContainerRef}
className={`absolute bg-default h-9 flex flex-wrap pl-1 -ml-1 ${ className={`absolute bg-default flex flex-wrap pl-1 -ml-1 ${
tagsContainerExpanded ? '' : 'overflow-hidden' inputOverflowed ? 'h-18' : 'h-9'
}`} } ${tagsContainerExpanded || !tagsOverflowed ? '' : 'overflow-hidden'}`}
style={{ style={{
maxWidth: tagsContainerMaxWidth, maxWidth: tagsContainerMaxWidth,
}} }}
@@ -152,16 +155,17 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
index={index} index={index}
tag={tag} tag={tag}
maxWidth={tagsContainerMaxWidth} maxWidth={tagsContainerMaxWidth}
overflowed={!tagsContainerExpanded && overflowed={
!tagsContainerExpanded &&
!!lastVisibleTagIndex && !!lastVisibleTagIndex &&
index > lastVisibleTagIndex} index > lastVisibleTagIndex
}
/> />
))} ))}
<AutocompleteTagInput <AutocompleteTagInput
application={application} application={application}
appState={appState} appState={appState}
tagsRef={tagsRef} tagsRef={tagsRef}
tabIndex={tagsOverflowed ? -1 : 0}
/> />
</div> </div>
{tagsOverflowed && ( {tagsOverflowed && (

View File

@@ -13,11 +13,12 @@ import { WebApplication } from '../application';
import { AppState } from './app_state'; import { AppState } from './app_state';
export class ActiveNoteState { export class ActiveNoteState {
inputOverflowed = false;
overflowedTagsCount = 0;
tags: SNTag[] = []; tags: SNTag[] = [];
tagsContainerMaxWidth: number | 'auto' = 0; tagsContainerMaxWidth: number | 'auto' = 0;
tagsContainerExpanded = false;
overflowedTagsCount = 0;
tagFocused = false; tagFocused = false;
tagsContainerExpanded = false;
constructor( constructor(
private application: WebApplication, private application: WebApplication,
@@ -25,18 +26,20 @@ export class ActiveNoteState {
appEventListeners: (() => void)[] appEventListeners: (() => void)[]
) { ) {
makeObservable(this, { makeObservable(this, {
tags: observable, inputOverflowed: observable,
tagsContainerMaxWidth: observable,
tagsContainerExpanded: observable,
overflowedTagsCount: observable, overflowedTagsCount: observable,
tags: observable,
tagFocused: observable, tagFocused: observable,
tagsContainerExpanded: observable,
tagsContainerMaxWidth: observable,
tagsOverflowed: computed, tagsOverflowed: computed,
setTagsContainerMaxWidth: action, setInputOverflowed: action,
setTagsContainerExpanded: action,
setOverflowedTagsCount: action, setOverflowedTagsCount: action,
setTagFocused: action, setTagFocused: action,
setTagsContainerExpanded: action,
setTagsContainerMaxWidth: action,
reloadTags: action, reloadTags: action,
}); });
@@ -58,12 +61,8 @@ export class ActiveNoteState {
return this.overflowedTagsCount > 0 && !this.tagsContainerExpanded; return this.overflowedTagsCount > 0 && !this.tagsContainerExpanded;
} }
setTagsContainerMaxWidth(width: number): void { setInputOverflowed(overflowed: boolean): void {
this.tagsContainerMaxWidth = width; this.inputOverflowed = overflowed;
}
setTagsContainerExpanded(expanded: boolean): void {
this.tagsContainerExpanded = expanded;
} }
setOverflowedTagsCount(count: number): void { setOverflowedTagsCount(count: number): void {
@@ -74,6 +73,14 @@ export class ActiveNoteState {
this.tagFocused = focused; this.tagFocused = focused;
} }
setTagsContainerExpanded(expanded: boolean): void {
this.tagsContainerExpanded = expanded;
}
setTagsContainerMaxWidth(width: number): void {
this.tagsContainerMaxWidth = width;
}
reloadTags(): void { reloadTags(): void {
const { activeNote } = this; const { activeNote } = this;
if (activeNote) { if (activeNote) {

View File

@@ -222,6 +222,10 @@
height: 2.5rem; height: 2.5rem;
} }
.h-18 {
height: 4.5rem;
}
.max-h-120 { .max-h-120 {
max-height: 30rem; max-height: 30rem;
} }