diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index eead8b4e8..5680c8704 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -64,7 +64,7 @@ import { MultipleSelectedNotesDirective } from './components/MultipleSelectedNot import { NotesContextMenuDirective } from './components/NotesContextMenu'; import { NotesOptionsPanelDirective } from './components/NotesOptionsPanel'; import { IconDirective } from './components/Icon'; -import { NoteTagsDirective } from './components/NoteTags'; +import { NoteTagsContainerDirective } from './components/NoteTagsContainer'; function reloadHiddenFirefoxTab(): boolean { /** @@ -159,7 +159,7 @@ const startApplication: StartApplication = async function startApplication( .directive('notesContextMenu', NotesContextMenuDirective) .directive('notesOptionsPanel', NotesOptionsPanelDirective) .directive('icon', IconDirective) - .directive('noteTags', NoteTagsDirective); + .directive('noteTagsContainer', NoteTagsContainerDirective); // Filters angular.module('app').filter('trusted', ['$sce', trusted]); diff --git a/app/assets/javascripts/components/NoteTag.tsx b/app/assets/javascripts/components/NoteTag.tsx new file mode 100644 index 000000000..f3f5a0d8d --- /dev/null +++ b/app/assets/javascripts/components/NoteTag.tsx @@ -0,0 +1,91 @@ +import { Icon } from './Icon'; +import { FunctionalComponent, RefObject } from 'preact'; +import { useRef, useState } from 'preact/hooks'; +import { AppState } from '@/ui_models/app_state'; +import { SNTag } from '@standardnotes/snjs/dist/@types'; + +type Props = { + appState: AppState; + index: number; + tagsRef: RefObject; + tag: SNTag; + overflowed: boolean; + maxWidth: number | 'auto'; +}; + +export const NoteTag: FunctionalComponent = ({ + appState, + index, + tagsRef, + tag, + overflowed, + maxWidth, +}) => { + const [showDeleteButton, setShowDeleteButton] = useState(false); + const deleteTagRef = useRef(); + + const deleteTag = async () => { + await appState.activeNote.removeTagFromActiveNote(tag); + + if (index > 0 && tagsRef.current) { + tagsRef.current[index - 1].focus(); + } + }; + + const onTagClick = () => { + appState.setSelectedTag(tag); + }; + + const onFocus = () => { + appState.activeNote.setTagFocused(true); + setShowDeleteButton(true); + }; + + const onBlur = (event: FocusEvent) => { + appState.activeNote.setTagFocused(false); + if ((event.relatedTarget as Node) !== deleteTagRef.current) { + setShowDeleteButton(false); + } + }; + + return ( + + )} + + ); +}; diff --git a/app/assets/javascripts/components/NoteTags.tsx b/app/assets/javascripts/components/NoteTagsContainer.tsx similarity index 69% rename from app/assets/javascripts/components/NoteTags.tsx rename to app/assets/javascripts/components/NoteTagsContainer.tsx index 5effdac7c..0b5724417 100644 --- a/app/assets/javascripts/components/NoteTags.tsx +++ b/app/assets/javascripts/components/NoteTagsContainer.tsx @@ -1,18 +1,18 @@ import { AppState } from '@/ui_models/app_state'; import { observer } from 'mobx-react-lite'; import { toDirective, useCloseOnClickOutside } from './utils'; -import { Icon } from './Icon'; import { AutocompleteTagInput } from './AutocompleteTagInput'; import { WebApplication } from '@/ui_models/application'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { SNTag } from '@standardnotes/snjs'; +import { NoteTag } from './NoteTag'; type Props = { application: WebApplication; appState: AppState; }; -const NoteTags = observer(({ application, appState }: Props) => { +const NoteTagsContainer = observer(({ application, appState }: Props) => { const { overflowedTagsCount, tags, @@ -29,31 +29,15 @@ const NoteTags = observer(({ application, appState }: Props) => { const containerRef = useRef(); const tagsContainerRef = useRef(); const tagsRef = useRef([]); - const overflowButtonRef = useRef(); tagsRef.current = []; useCloseOnClickOutside(tagsContainerRef, (expanded: boolean) => { - if (overflowButtonRef.current || tagsContainerExpanded) { + if (tagsContainerExpanded) { appState.activeNote.setTagsContainerExpanded(expanded); } }); - const onTagBackspacePress = async (tag: SNTag, index: number) => { - await appState.activeNote.removeTagFromActiveNote(tag); - - if (index > 0) { - tagsRef.current[index - 1].focus(); - } - }; - - const onTagClick = (clickedTag: SNTag) => { - const tagIndex = tags.findIndex((tag) => tag.uuid === clickedTag.uuid); - if (tagsRef.current[tagIndex] === document.activeElement) { - appState.setSelectedTag(clickedTag); - } - }; - const isTagOverflowed = useCallback( (tagElement?: HTMLButtonElement): boolean | undefined => { if (!tagElement) { @@ -144,10 +128,7 @@ const NoteTags = observer(({ application, appState }: Props) => { tagResizeObserver.disconnect(); } }; - }, [reloadTagsContainerLayout, tags]); - - 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`; + }, [reloadTagsContainerLayout]); return (
{ maxWidth: tagsContainerMaxWidth, }} > - {tags.map((tag: SNTag, index: number) => { - const overflowed = - !tagsContainerExpanded && - lastVisibleTagIndex && - index > lastVisibleTagIndex; - return ( - - ); - })} + {tags.map((tag: SNTag, index: number) => ( + lastVisibleTagIndex} + /> + ))} {
{tagsOverflowed && (