fix: adjust layout for all zoom values and font sizes

This commit is contained in:
Antonella Sgarlatta
2021-06-01 18:32:44 -03:00
parent 570a7ed587
commit a72815acbd
3 changed files with 128 additions and 93 deletions

View File

@@ -12,28 +12,25 @@ type Props = {
appState: AppState; appState: AppState;
}; };
const TAGS_ROW_HEIGHT = 36;
const MIN_OVERFLOW_TOP = 76;
const TAG_RIGHT_MARGIN = 8;
const NoteTags = observer(({ application, appState }: Props) => { const NoteTags = observer(({ application, appState }: Props) => {
const { const {
overflowedTagsCount, overflowedTagsCount,
tags, tags,
tagsContainerPosition,
tagsContainerMaxWidth, tagsContainerMaxWidth,
tagsContainerExpanded, tagsContainerExpanded,
tagsOverflowed, tagsOverflowed,
} = appState.activeNote; } = appState.activeNote;
const [containerHeight, setContainerHeight] = const [expandedContainerHeight, setExpandedContainerHeight] = useState(0);
useState(TAGS_ROW_HEIGHT); const [lastVisibleTagIndex, setLastVisibleTagIndex] =
useState<number | null>(null);
const [overflowCountPosition, setOverflowCountPosition] = useState(0); const [overflowCountPosition, setOverflowCountPosition] = useState(0);
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const tagsContainerRef = useRef<HTMLDivElement>(); const tagsContainerRef = useRef<HTMLDivElement>();
const tagsRef = useRef<HTMLButtonElement[]>([]); const tagsRef = useRef<HTMLButtonElement[]>([]);
const overflowButtonRef = useRef<HTMLButtonElement>(); const overflowButtonRef = useRef<HTMLButtonElement>();
tagsRef.current = []; tagsRef.current = [];
useCloseOnClickOutside(tagsContainerRef, (expanded: boolean) => { useCloseOnClickOutside(tagsContainerRef, (expanded: boolean) => {
@@ -42,16 +39,16 @@ const NoteTags = observer(({ application, appState }: Props) => {
} }
}); });
const onTagBackspacePress = async (tag: SNTag) => { const onTagBackspacePress = async (tag: SNTag, index: number) => {
await appState.activeNote.removeTagFromActiveNote(tag); await appState.activeNote.removeTagFromActiveNote(tag);
if (tagsRef.current.length > 1) { if (index > 0) {
tagsRef.current[tagsRef.current.length - 1].focus(); tagsRef.current[index - 1].focus();
} }
}; };
const onTagClick = (clickedTag: SNTag) => { const onTagClick = (clickedTag: SNTag) => {
const tagIndex = tags.findIndex(tag => tag.uuid === clickedTag.uuid); const tagIndex = tags.findIndex((tag) => tag.uuid === clickedTag.uuid);
if (tagsRef.current[tagIndex] === document.activeElement) { if (tagsRef.current[tagIndex] === document.activeElement) {
appState.setSelectedTag(clickedTag); appState.setSelectedTag(clickedTag);
} }
@@ -65,31 +62,29 @@ const NoteTags = observer(({ application, appState }: Props) => {
if (tagsContainerExpanded) { if (tagsContainerExpanded) {
return false; return false;
} }
return tagElement.getBoundingClientRect().top >= MIN_OVERFLOW_TOP; const firstTagTop = tagsRef.current[0].offsetTop;
return tagElement.offsetTop > firstTagTop;
}, },
[tagsContainerExpanded] [tagsContainerExpanded]
); );
const reloadOverflowCountPosition = useCallback(() => { const reloadLastVisibleTagIndex = useCallback(() => {
if (tagsContainerExpanded) {
return tags.length - 1;
}
const firstOverflowedTagIndex = tagsRef.current.findIndex((tagElement) => const firstOverflowedTagIndex = tagsRef.current.findIndex((tagElement) =>
isTagOverflowed(tagElement) isTagOverflowed(tagElement)
); );
if (tagsContainerExpanded || firstOverflowedTagIndex < 1) { if (firstOverflowedTagIndex > -1) {
return; setLastVisibleTagIndex(firstOverflowedTagIndex - 1);
} else {
setLastVisibleTagIndex(null);
} }
const previousTagRect = }, [isTagOverflowed, tags, tagsContainerExpanded]);
tagsRef.current[firstOverflowedTagIndex - 1].getBoundingClientRect();
const position =
previousTagRect.right - (tagsContainerPosition ?? 0) + TAG_RIGHT_MARGIN;
setOverflowCountPosition(position);
}, [isTagOverflowed, tagsContainerExpanded, tagsContainerPosition]);
const reloadContainersHeight = useCallback(() => { const reloadExpandedContainersHeight = useCallback(() => {
const containerHeight = tagsContainerExpanded setExpandedContainerHeight(tagsContainerRef.current.scrollHeight);
? tagsContainerRef.current.scrollHeight }, []);
: TAGS_ROW_HEIGHT;
setContainerHeight(containerHeight);
}, [tagsContainerExpanded]);
const reloadOverflowCount = useCallback(() => { const reloadOverflowCount = useCallback(() => {
const count = tagsRef.current.filter((tagElement) => const count = tagsRef.current.filter((tagElement) =>
@@ -98,72 +93,108 @@ const NoteTags = observer(({ application, appState }: Props) => {
appState.activeNote.setOverflowedTagsCount(count); appState.activeNote.setOverflowedTagsCount(count);
}, [appState.activeNote, isTagOverflowed]); }, [appState.activeNote, isTagOverflowed]);
const reloadOverflowCountPosition = useCallback(() => {
if (tagsContainerExpanded || !lastVisibleTagIndex) {
return;
}
const { offsetLeft: lastVisibleTagLeft, clientWidth: lastVisibleTagWidth } =
tagsRef.current[lastVisibleTagIndex];
console.log(tagsRef.current[0].offsetLeft);
setOverflowCountPosition(lastVisibleTagLeft + lastVisibleTagWidth);
}, [lastVisibleTagIndex, tagsContainerExpanded]);
const expandTags = () => { const expandTags = () => {
appState.activeNote.setTagsContainerExpanded(true); appState.activeNote.setTagsContainerExpanded(true);
}; };
useEffect(() => { const reloadTagsContainerLayout = useCallback(() => {
appState.activeNote.reloadTagsContainerLayout(); appState.activeNote.reloadTagsContainerLayout();
reloadOverflowCountPosition(); reloadLastVisibleTagIndex();
reloadContainersHeight(); reloadExpandedContainersHeight();
reloadOverflowCount(); reloadOverflowCount();
reloadOverflowCountPosition();
}, [ }, [
appState.activeNote, appState.activeNote,
reloadOverflowCountPosition, reloadLastVisibleTagIndex,
reloadContainersHeight, reloadExpandedContainersHeight,
reloadOverflowCount, reloadOverflowCount,
tags, reloadOverflowCountPosition,
]); ]);
useEffect(() => {
reloadTagsContainerLayout;
}, [
reloadTagsContainerLayout,
tags,
tagsContainerMaxWidth,
]);
useEffect(() => {
let tagResizeObserver: ResizeObserver;
if (ResizeObserver) {
tagResizeObserver = new ResizeObserver(() => {
reloadTagsContainerLayout();
});
tagsRef.current.forEach((tagElement) => tagResizeObserver.observe(tagElement));
}
return () => {
if (tagResizeObserver) {
tagResizeObserver.disconnect();
}
};
}, [reloadTagsContainerLayout]);
const tagClass = `h-6 bg-contrast border-0 rounded text-xs color-text py-1 pr-2 flex items-center const tagClass = `h-6 bg-contrast border-0 rounded text-xs color-text py-1 pr-2 flex items-center
mt-2 cursor-pointer hover:bg-secondary-contrast focus:bg-secondary-contrast`; mt-2 cursor-pointer hover:bg-secondary-contrast focus:bg-secondary-contrast`;
return ( return (
<div <div
className="flex transition-height duration-150" className="flex transition-height duration-150 h-9 relative"
ref={containerRef} ref={containerRef}
style={{ height: containerHeight }} style={tagsContainerExpanded ? { height: expandedContainerHeight } : {}}
> >
<div <div
ref={tagsContainerRef} ref={tagsContainerRef}
className={`absolute flex flex-wrap pl-1 -ml-1 ${ className={`absolute bg-default h-9 flex flex-wrap pl-1 -ml-1 ${
tagsContainerExpanded ? '' : 'overflow-hidden' tagsContainerExpanded ? '' : 'overflow-y-hidden'
}`} }`}
style={{ style={{
maxWidth: tagsContainerMaxWidth, maxWidth: tagsContainerMaxWidth,
height: TAGS_ROW_HEIGHT,
}} }}
> >
{tags.map((tag: SNTag, index: number) => ( {tags.map((tag: SNTag, index: number) => {
<button const overflowed =
className={`${tagClass} pl-1 mr-2 transition-opacity duration-150 ${ !tagsContainerExpanded &&
isTagOverflowed(tagsRef.current[index]) lastVisibleTagIndex &&
? 'opacity-0' index > lastVisibleTagIndex;
: 'opacity-1' return (
}`} <button
style={{ maxWidth: tagsContainerMaxWidth }} className={`${tagClass} pl-1 mr-2`}
ref={(element) => { style={{ maxWidth: tagsContainerMaxWidth }}
if (element) { ref={(element) => {
tagsRef.current[index] = element; if (element) {
} tagsRef.current[index] = element;
}} }
onClick={() => onTagClick(tag)} }}
onKeyUp={(event) => { onClick={() => onTagClick(tag)}
if (event.key === 'Backspace') { onKeyUp={(event) => {
onTagBackspacePress(tag); if (event.key === 'Backspace') {
} onTagBackspacePress(tag, index);
}} }
tabIndex={isTagOverflowed(tagsRef.current[index]) ? -1 : 0} }}
> tabIndex={overflowed ? -1 : 0}
<Icon >
type="hashtag" <Icon
className="sn-icon--small color-neutral mr-1" type="hashtag"
/> className="sn-icon--small color-neutral mr-1"
<span className="whitespace-nowrap overflow-hidden overflow-ellipsis"> />
{tag.title} <span className="whitespace-nowrap overflow-y-hidden overflow-ellipsis">
</span> {tag.title}
</button> </span>
))} </button>
);
})}
<AutocompleteTagInput <AutocompleteTagInput
application={application} application={application}
appState={appState} appState={appState}
@@ -175,9 +206,9 @@ const NoteTags = observer(({ application, appState }: Props) => {
<button <button
ref={overflowButtonRef} ref={overflowButtonRef}
type="button" type="button"
className={`${tagClass} pl-2 absolute`} className={`${tagClass} pl-2 ml-1 absolute`}
style={{ left: overflowCountPosition }}
onClick={expandTags} onClick={expandTags}
style={{ left: overflowCountPosition }}
> >
+{overflowedTagsCount} +{overflowedTagsCount}
</button> </button>

View File

@@ -14,7 +14,6 @@ import { AppState } from './app_state';
export class ActiveNoteState { export class ActiveNoteState {
tags: SNTag[] = []; tags: SNTag[] = [];
tagsContainerPosition? = 0;
tagsContainerMaxWidth: number | 'auto' = 'auto'; tagsContainerMaxWidth: number | 'auto' = 'auto';
tagsContainerExpanded = false; tagsContainerExpanded = false;
overflowedTagsCount = 0; overflowedTagsCount = 0;
@@ -26,24 +25,18 @@ export class ActiveNoteState {
) { ) {
makeObservable(this, { makeObservable(this, {
tags: observable, tags: observable,
tagsContainerPosition: observable,
tagsContainerMaxWidth: observable, tagsContainerMaxWidth: observable,
tagsContainerExpanded: observable, tagsContainerExpanded: observable,
overflowedTagsCount: observable, overflowedTagsCount: observable,
tagsOverflowed: computed, tagsOverflowed: computed,
setTagsContainerPosition: action,
setTagsContainerMaxWidth: action, setTagsContainerMaxWidth: action,
setTagsContainerExpanded: action, setTagsContainerExpanded: action,
setOverflowedTagsCount: action, setOverflowedTagsCount: action,
reloadTags: action, reloadTags: action,
}); });
this.tagsContainerPosition = document
.getElementById('editor-column')
?.getBoundingClientRect().left;
appEventListeners.push( appEventListeners.push(
application.streamItems( application.streamItems(
ContentType.Tag, ContentType.Tag,
@@ -62,10 +55,6 @@ export class ActiveNoteState {
return this.overflowedTagsCount > 0 && !this.tagsContainerExpanded; return this.overflowedTagsCount > 0 && !this.tagsContainerExpanded;
} }
setTagsContainerPosition(position: number): void {
this.tagsContainerPosition = position;
}
setTagsContainerMaxWidth(width: number): void { setTagsContainerMaxWidth(width: number): void {
this.tagsContainerMaxWidth = width; this.tagsContainerMaxWidth = width;
} }
@@ -86,16 +75,19 @@ export class ActiveNoteState {
} }
reloadTagsContainerLayout(): void { reloadTagsContainerLayout(): void {
const MARGIN = 72;
const EDITOR_ELEMENT_ID = 'editor-column'; const EDITOR_ELEMENT_ID = 'editor-column';
const { clientWidth } = document.documentElement; const defaultFontSize = window.getComputedStyle(
const editorPosition = document.documentElement
document.getElementById(EDITOR_ELEMENT_ID)?.getBoundingClientRect() ).fontSize;
.left ?? 0; const containerMargins = parseFloat(defaultFontSize) * 4;
this.appState.activeNote.setTagsContainerPosition(editorPosition); const editorWidth =
this.appState.activeNote.setTagsContainerMaxWidth( document.getElementById(EDITOR_ELEMENT_ID)?.clientWidth;
clientWidth - editorPosition - MARGIN
); if (editorWidth) {
this.appState.activeNote.setTagsContainerMaxWidth(
editorWidth - containerMargins
);
}
} }
async addTagToActiveNote(tag: SNTag): Promise<void> { async addTagToActiveNote(tag: SNTag): Promise<void> {

View File

@@ -57,6 +57,10 @@
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.ml-1 {
margin-left: 0.25rem;
}
.mr-1 { .mr-1 {
margin-right: 0.25rem; margin-right: 0.25rem;
} }
@@ -192,6 +196,10 @@
height: 2rem; height: 2rem;
} }
.h-9 {
height: 2.25rem;
}
.h-10 { .h-10 {
height: 2.5rem; height: 2.5rem;
} }
@@ -212,8 +220,8 @@
overflow: auto; overflow: auto;
} }
.overflow-hidden { .overflow-y-hidden {
overflow: hidden; overflow-y: hidden;
} }
.overflow-ellipsis { .overflow-ellipsis {
@@ -260,6 +268,10 @@
width: 20rem; width: 20rem;
} }
.relative {
position: relative;
}
/** /**
* A button that is just an icon. Separated from .sn-button because there * A button that is just an icon. Separated from .sn-button because there
* is almost no style overlap. * is almost no style overlap.