From 7ac58562058004faad039e8684e1795fd13837a1 Mon Sep 17 00:00:00 2001 From: Antonella Sgarlatta Date: Thu, 27 May 2021 21:17:39 -0300 Subject: [PATCH] feat: make tags container expandable --- .../components/AutocompleteTagInput.tsx | 8 +- .../javascripts/components/NoteTags.tsx | 145 +++++++++++++++--- .../components/NotesOptionsPanel.tsx | 2 +- .../javascripts/views/editor/editor-view.pug | 11 +- 4 files changed, 136 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/components/AutocompleteTagInput.tsx b/app/assets/javascripts/components/AutocompleteTagInput.tsx index baa9b3edf..78bc1718a 100644 --- a/app/assets/javascripts/components/AutocompleteTagInput.tsx +++ b/app/assets/javascripts/components/AutocompleteTagInput.tsx @@ -10,13 +10,13 @@ import { AppState } from '@/ui_models/app_state'; type Props = { application: WebApplication; appState: AppState; - lastTagRef: RefObject; + tagsRef: RefObject }; export const AutocompleteTagInput: FunctionalComponent = ({ application, appState, - lastTagRef, + tagsRef, }) => { const [searchQuery, setSearchQuery] = useState(''); const [dropdownVisible, setDropdownVisible] = useState(false); @@ -100,8 +100,8 @@ export const AutocompleteTagInput: FunctionalComponent = ({ onBlur={closeOnBlur} onFocus={showDropdown} onKeyUp={(event) => { - if (event.key === 'Backspace' && searchQuery === '') { - lastTagRef.current?.focus(); + if (event.key === 'Backspace' && searchQuery === '' && tagsRef.current && tagsRef.current.length > 1) { + tagsRef.current[tagsRef.current.length - 1].focus(); } }} /> diff --git a/app/assets/javascripts/components/NoteTags.tsx b/app/assets/javascripts/components/NoteTags.tsx index ad6fb70fd..a1c6cd798 100644 --- a/app/assets/javascripts/components/NoteTags.tsx +++ b/app/assets/javascripts/components/NoteTags.tsx @@ -4,7 +4,7 @@ import { toDirective } from './utils'; import { Icon } from './Icon'; import { AutocompleteTagInput } from './AutocompleteTagInput'; import { WebApplication } from '@/ui_models/application'; -import { useRef } from 'preact/hooks'; +import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { SNTag } from '@standardnotes/snjs'; type Props = { @@ -12,35 +12,142 @@ type Props = { appState: AppState; }; +const TAGS_ROW_RIGHT_MARGIN = 92; +const TAGS_ROW_HEIGHT = 32; +const MIN_OVERFLOW_TOP = 76; +const TAG_RIGHT_MARGIN = 8; + const NoteTags = observer(({ application, appState }: Props) => { const { activeNoteTags } = appState.notes; - const lastTagRef = useRef(); + const [tagsContainerMaxWidth, setTagsContainerMaxWidth] = + useState('auto'); + const [overflowedTagsCount, setOverflowedTagsCount] = useState(0); + const [overflowCountPosition, setOverflowCountPosition] = useState(0); + const [tagsContainerCollapsed, setTagsContainerCollapsed] = useState(true); + const [containerHeight, setContainerHeight] = useState(TAGS_ROW_HEIGHT); + + const containerRef = useRef(); + const tagsContainerRef = useRef(); + const tagsRef = useRef([]); + tagsRef.current = []; const onTagBackspacePress = async (tag: SNTag) => { await appState.notes.removeTagFromActiveNote(tag); - lastTagRef.current?.focus(); + + if (tagsRef.current.length > 1) { + tagsRef.current[tagsRef.current.length - 1].focus(); + } }; + const reloadOverflowCount = useCallback(() => { + const editorElement = document.getElementById('editor-column'); + let overflowCount = 0; + for (const [index, tagElement] of tagsRef.current.entries()) { + if (tagElement.getBoundingClientRect().top >= MIN_OVERFLOW_TOP) { + if (overflowCount === 0) { + setOverflowCountPosition( + tagsRef.current[index - 1].getBoundingClientRect().right - + (editorElement ? editorElement.getBoundingClientRect().left : 0) + + TAG_RIGHT_MARGIN + ); + } + overflowCount += 1; + } + } + setOverflowedTagsCount(overflowCount); + + if (!tagsContainerCollapsed) { + setContainerHeight(tagsContainerRef.current.scrollHeight); + } + }, [tagsContainerCollapsed]); + + const expandTags = () => { + setContainerHeight(tagsContainerRef.current.scrollHeight); + setTagsContainerCollapsed(false); + }; + + useEffect(() => { + const editorElement = document.getElementById('editor-column'); + const resizeObserver = new ResizeObserver((entries) => { + const entry = entries[0]; + const { width } = entry.contentRect; + setTagsContainerMaxWidth(width); + reloadOverflowCount(); + }); + + if (editorElement) { + resizeObserver.observe(editorElement); + } + + return () => { + resizeObserver.disconnect(); + }; + }, [reloadOverflowCount]); + + useEffect(() => { + reloadOverflowCount(); + }, [activeNoteTags, reloadOverflowCount]); + + const tagClass = `bg-contrast border-0 rounded text-xs color-text py-1 pr-2 flex items-center + mt-2 mr-2 cursor-pointer hover:bg-secondary-contrast focus:bg-secondary-contrast`; + return ( -
- {activeNoteTags.map((tag, index) => ( +
+
+ {activeNoteTags.map((tag, index) => ( + + ))} + +
+ {overflowedTagsCount > 1 && tagsContainerCollapsed && ( - ))} - + )}
); }); diff --git a/app/assets/javascripts/components/NotesOptionsPanel.tsx b/app/assets/javascripts/components/NotesOptionsPanel.tsx index d9dea3b72..2b5a3bdbd 100644 --- a/app/assets/javascripts/components/NotesOptionsPanel.tsx +++ b/app/assets/javascripts/components/NotesOptionsPanel.tsx @@ -54,7 +54,7 @@ export const NotesOptionsPanel = observer(({ appState }: Props) => { }} onBlur={closeOnBlur} ref={buttonRef} - className="sn-icon-button" + className="sn-icon-button mt-2" > Actions diff --git a/app/assets/javascripts/views/editor/editor-view.pug b/app/assets/javascripts/views/editor/editor-view.pug index 780517482..87cd64c80 100644 --- a/app/assets/javascripts/views/editor/editor-view.pug +++ b/app/assets/javascripts/views/editor/editor-view.pug @@ -24,7 +24,7 @@ ng-if="self.showLockedIcon" ) | {{self.lockText}} - #editor-title-bar.section-title-bar.flex.items-center.justify-between.w-full( + #editor-title-bar.section-title-bar.flex.items-start.justify-between.w-full( ng-show='self.note && !self.note.errorDecrypting' ) div.flex-grow( @@ -41,11 +41,10 @@ select-on-focus='true', spellcheck='false' ) - .editor-tags - note-tags( - application='self.application' - app-state='self.appState' - ) + note-tags( + application='self.application' + app-state='self.appState' + ) div.flex.items-center #save-status .message(