refactor: allow opening tag context menu without selecting the tag first

This commit is contained in:
Aman Harwara
2023-11-20 15:22:10 +05:30
parent 25e3dd50b6
commit 0f938b4cd1
6 changed files with 101 additions and 111 deletions

View File

@@ -7,6 +7,7 @@ import { getEmojiLength } from './EmojiLength'
import Icon, { isIconEmoji } from './Icon'
import { IconNameToSvgMapping } from './IconNameToSvgMapping'
import { IconPickerType } from './IconPickerType'
import DecoratedInput from '../Input/DecoratedInput'
type Props = {
selectedValue: VectorIconNameOrEmoji
@@ -114,13 +115,13 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className, useIconG
}, [])
return (
<div className={`flex h-full flex-grow flex-col overflow-auto ${className}`}>
<div className={`flex h-full flex-grow flex-col ${className}`}>
<div className="flex">
<TabButton label="Icon" type={'icon'} currentType={currentType} selectTab={selectTab} />
<TabButton label="Emoji" type={'emoji'} currentType={currentType} selectTab={selectTab} />
<TabButton label="Reset" type={'reset'} currentType={currentType} selectTab={selectTab} />
</div>
<div className={'mt-2 h-full min-h-0 overflow-auto'}>
<div className={classNames('mt-1 h-full min-h-0', currentType === 'icon' && 'overflow-auto')}>
{currentType === 'icon' &&
(useIconGrid ? (
<div
@@ -152,17 +153,14 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className, useIconG
))}
{currentType === 'emoji' && (
<>
<div>
<input
ref={emojiInputRef}
autoComplete="off"
autoFocus={emojiInputFocused}
className="w-full flex-grow rounded border border-solid border-passive-3 bg-default px-2 py-1 text-base font-bold text-text focus:shadow-none focus:outline-none"
type="text"
value={emojiInputValue as string}
onChange={({ target: input }) => handleEmojiChange((input as HTMLInputElement)?.value)}
/>
</div>
<DecoratedInput
ref={emojiInputRef}
autocomplete={false}
autofocus={emojiInputFocused}
type="text"
value={emojiInputValue as string}
onChange={(value) => handleEmojiChange(value)}
/>
<div className="mt-2 text-sm text-passive-0 lg:text-xs">
Use your keyboard to enter or paste in an emoji character.
</div>

View File

@@ -1,5 +1,5 @@
import { observer } from 'mobx-react-lite'
import { useCallback, useMemo } from 'react'
import { useCallback, useMemo, useRef } from 'react'
import Icon from '@/Components/Icon/Icon'
import Menu from '@/Components/Menu/Menu'
import MenuItem from '@/Components/Menu/MenuItem'
@@ -14,6 +14,8 @@ import IconPicker from '../Icon/IconPicker'
import AddToVaultMenuOption from '../Vaults/AddToVaultMenuOption'
import { useApplication } from '../ApplicationProvider'
import MenuSection from '../Menu/MenuSection'
import DecoratedInput from '../Input/DecoratedInput'
import { KeyboardKey } from '@standardnotes/ui-services'
type ContextMenuProps = {
navigationController: NavigationController
@@ -38,11 +40,6 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
navigationController.setAddingSubtagTo(selectedTag)
}, [isEntitledToFolders, navigationController, selectedTag, premiumModal])
const onClickRename = useCallback(() => {
navigationController.setContextMenuOpen(false)
navigationController.setEditingTag(selectedTag)
}, [navigationController, selectedTag])
const onClickDelete = useCallback(() => {
navigationController.remove(selectedTag, true).catch(console.error)
}, [navigationController, selectedTag])
@@ -63,6 +60,26 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
const tagCreatedAt = useMemo(() => formatDateForContextMenu(selectedTag.created_at), [selectedTag.created_at])
const titleInputRef = useRef<HTMLInputElement>(null)
const saveTitle = useCallback(
(closeMenu = false) => {
if (!titleInputRef.current) {
return
}
const value = titleInputRef.current.value.trim()
navigationController
.save(selectedTag, value)
.catch(console.error)
.finally(() => {
if (closeMenu) {
navigationController.setContextMenuOpen(false)
}
})
},
[navigationController, selectedTag],
)
return (
<Popover
title="Tag options"
@@ -71,13 +88,41 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
togglePopover={() => navigationController.setContextMenuOpen(!contextMenuOpen)}
className="py-2"
>
<div className="flex flex-col gap-1 px-4 py-0.5 text-mobile-menu-item md:px-3 md:text-tablet-menu-item lg:text-menu-item">
<div className="font-semibold">Name</div>
<div className="flex gap-2.5">
<DecoratedInput
ref={titleInputRef}
className={{
container: 'flex-grow',
input: 'text-mobile-menu-item md:text-tablet-menu-item lg:text-menu-item',
}}
defaultValue={selectedTag.title}
key={selectedTag.uuid}
onBlur={() => saveTitle()}
onKeyDown={(event) => {
if (event.key === KeyboardKey.Enter) {
saveTitle(true)
}
}}
/>
<button
aria-label="Save tag name"
className="rounded border border-border bg-transparent px-1.5 active:bg-default translucent-ui:border-[--popover-border-color] md:hidden"
onClick={() => saveTitle(true)}
>
<Icon type="check" />
</button>
</div>
</div>
<HorizontalSeparator classes="my-2" />
<Menu a11yLabel="Tag context menu">
<IconPicker
key={'icon-picker'}
key={selectedTag.uuid}
onIconChange={handleIconChange}
selectedValue={selectedTag.iconString}
platform={application.platform}
className={'px-3 py-1.5'}
className={'py-1.5 md:px-3'}
useIconGrid={true}
iconGridClassName="max-h-30"
/>
@@ -98,10 +143,6 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
</div>
{!isEntitledToFolders && <Icon type={PremiumFeatureIconName} className={PremiumFeatureIconClass} />}
</MenuItem>
<MenuItem className={'py-1.5'} onClick={onClickRename}>
<Icon type="pencil-filled" className="mr-2 text-neutral" />
Rename
</MenuItem>
<MenuItem className={'py-1.5'} onClick={onClickDelete}>
<Icon type="trash" className="mr-2 text-danger" />
<span className="text-danger">Delete</span>
@@ -109,7 +150,7 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
</MenuSection>
</Menu>
<HorizontalSeparator classes="my-2" />
<div className="px-3 pb-1.5 pt-1 text-sm font-medium text-neutral lg:text-xs">
<div className="px-4 pb-1.5 pt-1 text-sm font-medium text-neutral md:px-3 lg:text-xs">
<div className="mb-1">
<span className="font-semibold">Last modified:</span> {tagLastModified}
</div>

View File

@@ -10,7 +10,7 @@ type Props = {
}
const TagContextMenuWrapper = ({ navigationController, featuresController }: Props) => {
const selectedTag = navigationController.selected
const selectedTag = navigationController.contextMenuTag
if (!selectedTag || !(selectedTag instanceof SNTag)) {
return null

View File

@@ -17,25 +17,19 @@ const TagsList: FunctionComponent<Props> = ({ type }: Props) => {
type === 'all' ? application.navigationController.allLocalRootTags : application.navigationController.starredTags
const openTagContextMenu = useCallback(
(posX: number, posY: number) => {
application.navigationController.setContextMenuClickLocation({
x: posX,
y: posY,
})
application.navigationController.reloadContextMenuLayout()
(x: number, y: number) => {
application.navigationController.setContextMenuClickLocation({ x, y })
application.navigationController.setContextMenuOpen(true)
},
[application],
)
const onContextMenu = useCallback(
(tag: SNTag, posX: number, posY: number) => {
if (application.navigationController.selected !== tag) {
void application.navigationController.setSelectedTag(tag, type)
}
(tag: SNTag, section: TagListSectionType, posX: number, posY: number) => {
application.navigationController.setContextMenuTag(tag, section)
openTagContextMenu(posX, posY)
},
[application, openTagContextMenu, type],
[application, openTagContextMenu],
)
return (

View File

@@ -34,7 +34,7 @@ type Props = {
features: FeaturesController
linkingController: LinkingController
level: number
onContextMenu: (tag: SNTag, posX: number, posY: number) => void
onContextMenu: (tag: SNTag, section: TagListSectionType, posX: number, posY: number) => void
}
const PADDING_BASE_PX = 14
@@ -50,9 +50,17 @@ export const TagsListItem: FunctionComponent<Props> = observer(
const subtagInputRef = useRef<HTMLInputElement>(null)
const menuButtonRef = useRef<HTMLAnchorElement>(null)
const isContextMenuOpenForTag =
navigationController.contextMenuTag === tag &&
navigationController.contextMenuOpen &&
navigationController.contextMenuTagSection === type
const isSelected = navigationController.selected === tag && navigationController.selectedLocation === type
const isEditing = navigationController.editingTag === tag && navigationController.selectedLocation === type
const isAddingSubtag = navigationController.addingSubtagTo === tag && navigationController.selectedLocation === type
const isAddingSubtag =
navigationController.addingSubtagTo === tag &&
(navigationController.contextMenuTag === tag
? navigationController.contextMenuTagSection === type
: navigationController.selectedLocation === type)
const noteCounts = computed(() => navigationController.getNotesCount(tag))
const childrenTags = computed(() => navigationController.getChildren(tag)).get()
@@ -164,10 +172,10 @@ export const TagsListItem: FunctionComponent<Props> = observer(
if (contextMenuOpen) {
navigationController.setContextMenuOpen(false)
} else {
onContextMenu(tag, menuButtonRect.right, menuButtonRect.top)
onContextMenu(tag, type, menuButtonRect.right, menuButtonRect.top)
}
},
[onContextMenu, navigationController, tag],
[navigationController, onContextMenu, tag, type],
)
const tagRef = useRef<HTMLDivElement>(null)
@@ -262,7 +270,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
className={classNames(
'tag group px-3.5 py-1 md:py-0',
isSelected && 'selected',
(isSelected || isContextMenuOpenForTag) && 'selected',
isBeingDraggedOver && 'is-drag-over',
)}
onClick={selectCurrentTag}
@@ -272,7 +280,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
}}
onContextMenu={(e) => {
e.preventDefault()
onContextMenu(tag, e.clientX, e.clientY)
onContextMenu(tag, type, e.clientX, e.clientY)
}}
draggable={true}
onDragStart={onDragStart}