chore: only show links container toggle if container can be truncated (#2326)
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
import { FormEventHandler, KeyboardEventHandler, useDeferredValue, useEffect, useRef } from 'react'
|
||||
import {
|
||||
FormEventHandler,
|
||||
ForwardedRef,
|
||||
KeyboardEventHandler,
|
||||
forwardRef,
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
@@ -13,6 +21,7 @@ import { Slot } from '@radix-ui/react-slot'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { mergeRefs } from '@/Hooks/mergeRefs'
|
||||
|
||||
type Props = {
|
||||
linkingController: LinkingController
|
||||
@@ -23,118 +32,116 @@ type Props = {
|
||||
item: DecryptedItem
|
||||
}
|
||||
|
||||
const ItemLinkAutocompleteInput = ({
|
||||
linkingController,
|
||||
focusPreviousItem,
|
||||
focusedId,
|
||||
setFocusedId,
|
||||
hoverLabel,
|
||||
item,
|
||||
}: Props) => {
|
||||
const application = useApplication()
|
||||
const ItemLinkAutocompleteInput = forwardRef(
|
||||
(
|
||||
{ linkingController, focusPreviousItem, focusedId, setFocusedId, hoverLabel, item }: Props,
|
||||
forwardedRef: ForwardedRef<HTMLInputElement>,
|
||||
) => {
|
||||
const application = useApplication()
|
||||
|
||||
const { getLinkedTagsForItem, linkItems, createAndAddNewTag, isEntitledToNoteLinking } = linkingController
|
||||
const { getLinkedTagsForItem, linkItems, createAndAddNewTag, isEntitledToNoteLinking } = linkingController
|
||||
|
||||
const tagsLinkedToItem = getLinkedTagsForItem(item) || []
|
||||
const tagsLinkedToItem = getLinkedTagsForItem(item) || []
|
||||
|
||||
const combobox = useComboboxStore()
|
||||
const value = combobox.useState('value')
|
||||
const searchQuery = useDeferredValue(value)
|
||||
const combobox = useComboboxStore()
|
||||
const value = combobox.useState('value')
|
||||
const searchQuery = useDeferredValue(value)
|
||||
|
||||
const { unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, item)
|
||||
const { unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, item)
|
||||
|
||||
const inputRef = useRef<HTMLInputElement | null>(null)
|
||||
const inputRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const onFormSubmit: FormEventHandler = async (event) => {
|
||||
event.preventDefault()
|
||||
if (searchQuery !== '') {
|
||||
await createAndAddNewTag(searchQuery)
|
||||
combobox.setValue('')
|
||||
const onFormSubmit: FormEventHandler = async (event) => {
|
||||
event.preventDefault()
|
||||
if (searchQuery !== '') {
|
||||
await createAndAddNewTag(searchQuery)
|
||||
combobox.setValue('')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
if (focusedId !== ElementIds.ItemLinkAutocompleteInput) {
|
||||
setFocusedId(ElementIds.ItemLinkAutocompleteInput)
|
||||
const handleFocus = () => {
|
||||
if (focusedId !== ElementIds.ItemLinkAutocompleteInput) {
|
||||
setFocusedId(ElementIds.ItemLinkAutocompleteInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyDown: KeyboardEventHandler = (event) => {
|
||||
switch (event.key) {
|
||||
case KeyboardKey.Left:
|
||||
if (searchQuery.length === 0) {
|
||||
focusPreviousItem()
|
||||
}
|
||||
break
|
||||
const onKeyDown: KeyboardEventHandler = (event) => {
|
||||
switch (event.key) {
|
||||
case KeyboardKey.Left:
|
||||
if (searchQuery.length === 0) {
|
||||
focusPreviousItem()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (focusedId === ElementIds.ItemLinkAutocompleteInput) {
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}, [focusedId])
|
||||
useEffect(() => {
|
||||
if (focusedId === ElementIds.ItemLinkAutocompleteInput) {
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}, [focusedId])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={onFormSubmit}>
|
||||
<label>
|
||||
<VisuallyHidden>Link tags, notes or files</VisuallyHidden>
|
||||
<Combobox
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={onFormSubmit}>
|
||||
<label>
|
||||
<VisuallyHidden>Link tags, notes or files</VisuallyHidden>
|
||||
<Combobox
|
||||
store={combobox}
|
||||
placeholder="Link tags, notes, files..."
|
||||
className={classNames(
|
||||
`${tagsLinkedToItem.length > 0 ? 'w-80' : 'mr-10 w-70'}`,
|
||||
'h-7 w-70 bg-transparent text-sm text-text focus:border-b-2 focus:border-info focus:shadow-none focus:outline-none lg:text-xs',
|
||||
)}
|
||||
title={hoverLabel}
|
||||
id={ElementIds.ItemLinkAutocompleteInput}
|
||||
ref={mergeRefs([inputRef, forwardedRef])}
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</label>
|
||||
<ComboboxPopover
|
||||
store={combobox}
|
||||
placeholder="Link tags, notes, files..."
|
||||
className={classNames(
|
||||
`${tagsLinkedToItem.length > 0 ? 'w-80' : 'mr-10 w-70'}`,
|
||||
'h-7 w-70 bg-transparent text-sm text-text focus:border-b-2 focus:border-info focus:shadow-none focus:outline-none lg:text-xs',
|
||||
'z-dropdown-menu max-h-[var(--popover-available-height)] w-[var(--popover-anchor-width)] overflow-y-auto rounded bg-default py-2 shadow-main',
|
||||
unlinkedItems.length === 0 && !shouldShowCreateTag && 'hidden',
|
||||
)}
|
||||
title={hoverLabel}
|
||||
id={ElementIds.ItemLinkAutocompleteInput}
|
||||
ref={inputRef}
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</label>
|
||||
<ComboboxPopover
|
||||
store={combobox}
|
||||
className={classNames(
|
||||
'z-dropdown-menu max-h-[var(--popover-available-height)] w-[var(--popover-anchor-width)] overflow-y-auto rounded bg-default py-2 shadow-main',
|
||||
unlinkedItems.length === 0 && !shouldShowCreateTag && 'hidden',
|
||||
)}
|
||||
>
|
||||
{unlinkedItems.map((result) => {
|
||||
const cannotLinkItem = !isEntitledToNoteLinking && result instanceof SNNote
|
||||
>
|
||||
{unlinkedItems.map((result) => {
|
||||
const cannotLinkItem = !isEntitledToNoteLinking && result instanceof SNNote
|
||||
|
||||
return (
|
||||
return (
|
||||
<ComboboxItem
|
||||
key={result.uuid}
|
||||
className="flex w-full cursor-pointer items-center justify-between gap-4 overflow-hidden py-2 px-3 hover:bg-contrast hover:text-foreground [&[data-active-item]]:bg-info-backdrop"
|
||||
hideOnClick
|
||||
onClick={() => {
|
||||
linkItems(item, result).catch(console.error)
|
||||
combobox.setValue('')
|
||||
}}
|
||||
>
|
||||
<LinkedItemMeta item={result} searchQuery={searchQuery} />
|
||||
{cannotLinkItem && <Icon type={PremiumFeatureIconName} className="ml-auto flex-shrink-0 text-info" />}
|
||||
</ComboboxItem>
|
||||
)
|
||||
})}
|
||||
{shouldShowCreateTag && (
|
||||
<ComboboxItem
|
||||
key={result.uuid}
|
||||
className="flex w-full cursor-pointer items-center justify-between gap-4 overflow-hidden py-2 px-3 hover:bg-contrast hover:text-foreground [&[data-active-item]]:bg-info-backdrop"
|
||||
hideOnClick
|
||||
as={Slot}
|
||||
onClick={() => {
|
||||
linkItems(item, result).catch(console.error)
|
||||
void createAndAddNewTag(searchQuery)
|
||||
combobox.setValue('')
|
||||
}}
|
||||
>
|
||||
<LinkedItemMeta item={result} searchQuery={searchQuery} />
|
||||
{cannotLinkItem && <Icon type={PremiumFeatureIconName} className="ml-auto flex-shrink-0 text-info" />}
|
||||
<LinkedItemSearchResultsAddTagOption searchQuery={searchQuery} />
|
||||
</ComboboxItem>
|
||||
)
|
||||
})}
|
||||
{shouldShowCreateTag && (
|
||||
<ComboboxItem
|
||||
hideOnClick
|
||||
as={Slot}
|
||||
onClick={() => {
|
||||
void createAndAddNewTag(searchQuery)
|
||||
combobox.setValue('')
|
||||
}}
|
||||
>
|
||||
<LinkedItemSearchResultsAddTagOption searchQuery={searchQuery} />
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxPopover>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ComboboxPopover>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default observer(ItemLinkAutocompleteInput)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { observer } from 'mobx-react-lite'
|
||||
import ItemLinkAutocompleteInput from './ItemLinkAutocompleteInput'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import LinkedItemBubble from './LinkedItemBubble'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
|
||||
import { ElementIds } from '@/Constants/ElementIDs'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
@@ -112,6 +112,34 @@ const LinkedItemBubblesContainer = ({ item, linkingController, hideToggle = fals
|
||||
const visibleItems = isCollapsed ? itemsToDisplay.slice(0, 5) : itemsToDisplay
|
||||
const nonVisibleItems = itemsToDisplay.length - visibleItems.length
|
||||
|
||||
const [canShowContainerToggle, setCanShowContainerToggle] = useState(false)
|
||||
const linkInputRef = useRef<HTMLInputElement>(null)
|
||||
const linkContainerRef = useRef<HTMLDivElement>(null)
|
||||
useEffect(() => {
|
||||
const container = linkContainerRef.current
|
||||
const linkInput = linkInputRef.current
|
||||
|
||||
if (!container || !linkInput) {
|
||||
return
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (container.clientHeight > linkInput.clientHeight) {
|
||||
setCanShowContainerToggle(true)
|
||||
} else {
|
||||
setCanShowContainerToggle(false)
|
||||
}
|
||||
})
|
||||
|
||||
resizeObserver.observe(linkContainerRef.current)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const shouldHideToggle = hideToggle || (!canShowContainerToggle && !isCollapsed)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -126,6 +154,7 @@ const LinkedItemBubblesContainer = ({ item, linkingController, hideToggle = fals
|
||||
allItemsLinkedToItem.length || notesLinkingToItem.length ? 'mt-1' : 'mt-0.5',
|
||||
isCollapsed ? 'overflow-hidden' : 'flex-wrap',
|
||||
)}
|
||||
ref={linkContainerRef}
|
||||
>
|
||||
{visibleItems.map((link) => (
|
||||
<LinkedItemBubble
|
||||
@@ -148,9 +177,10 @@ const LinkedItemBubblesContainer = ({ item, linkingController, hideToggle = fals
|
||||
setFocusedId={setFocusedId}
|
||||
hoverLabel={`Focus input to add a link (${shortcut})`}
|
||||
item={item}
|
||||
ref={linkInputRef}
|
||||
/>
|
||||
</div>
|
||||
{itemsToDisplay.length > 0 && !hideToggle && (
|
||||
{itemsToDisplay.length > 0 && !shouldHideToggle && (
|
||||
<RoundIconButton
|
||||
id="toggle-linking-container"
|
||||
label="Toggle linked items container"
|
||||
|
||||
Reference in New Issue
Block a user