chore: only show links container toggle if container can be truncated (#2326)

This commit is contained in:
Aman Harwara
2023-05-05 22:09:33 +05:30
committed by GitHub
parent 8c1f4bd064
commit 60e51d334c
2 changed files with 129 additions and 92 deletions

View File

@@ -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 { observer } from 'mobx-react-lite'
import { classNames } from '@standardnotes/utils' import { classNames } from '@standardnotes/utils'
import { LinkingController } from '@/Controllers/LinkingController' import { LinkingController } from '@/Controllers/LinkingController'
@@ -13,6 +21,7 @@ import { Slot } from '@radix-ui/react-slot'
import Icon from '../Icon/Icon' import Icon from '../Icon/Icon'
import { PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon' import { PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
import { KeyboardKey } from '@standardnotes/ui-services' import { KeyboardKey } from '@standardnotes/ui-services'
import { mergeRefs } from '@/Hooks/mergeRefs'
type Props = { type Props = {
linkingController: LinkingController linkingController: LinkingController
@@ -23,118 +32,116 @@ type Props = {
item: DecryptedItem item: DecryptedItem
} }
const ItemLinkAutocompleteInput = ({ const ItemLinkAutocompleteInput = forwardRef(
linkingController, (
focusPreviousItem, { linkingController, focusPreviousItem, focusedId, setFocusedId, hoverLabel, item }: Props,
focusedId, forwardedRef: ForwardedRef<HTMLInputElement>,
setFocusedId, ) => {
hoverLabel, const application = useApplication()
item,
}: Props) => {
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 combobox = useComboboxStore()
const value = combobox.useState('value') const value = combobox.useState('value')
const searchQuery = useDeferredValue(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) => { const onFormSubmit: FormEventHandler = async (event) => {
event.preventDefault() event.preventDefault()
if (searchQuery !== '') { if (searchQuery !== '') {
await createAndAddNewTag(searchQuery) await createAndAddNewTag(searchQuery)
combobox.setValue('') combobox.setValue('')
}
} }
}
const handleFocus = () => { const handleFocus = () => {
if (focusedId !== ElementIds.ItemLinkAutocompleteInput) { if (focusedId !== ElementIds.ItemLinkAutocompleteInput) {
setFocusedId(ElementIds.ItemLinkAutocompleteInput) setFocusedId(ElementIds.ItemLinkAutocompleteInput)
}
} }
}
const onKeyDown: KeyboardEventHandler = (event) => { const onKeyDown: KeyboardEventHandler = (event) => {
switch (event.key) { switch (event.key) {
case KeyboardKey.Left: case KeyboardKey.Left:
if (searchQuery.length === 0) { if (searchQuery.length === 0) {
focusPreviousItem() focusPreviousItem()
} }
break break
}
} }
}
useEffect(() => { useEffect(() => {
if (focusedId === ElementIds.ItemLinkAutocompleteInput) { if (focusedId === ElementIds.ItemLinkAutocompleteInput) {
inputRef.current?.focus() inputRef.current?.focus()
} }
}, [focusedId]) }, [focusedId])
return ( return (
<div> <div>
<form onSubmit={onFormSubmit}> <form onSubmit={onFormSubmit}>
<label> <label>
<VisuallyHidden>Link tags, notes or files</VisuallyHidden> <VisuallyHidden>Link tags, notes or files</VisuallyHidden>
<Combobox <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} store={combobox}
placeholder="Link tags, notes, files..."
className={classNames( className={classNames(
`${tagsLinkedToItem.length > 0 ? 'w-80' : 'mr-10 w-70'}`, 'z-dropdown-menu max-h-[var(--popover-available-height)] w-[var(--popover-anchor-width)] overflow-y-auto rounded bg-default py-2 shadow-main',
'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', unlinkedItems.length === 0 && !shouldShowCreateTag && 'hidden',
)} )}
title={hoverLabel} >
id={ElementIds.ItemLinkAutocompleteInput} {unlinkedItems.map((result) => {
ref={inputRef} const cannotLinkItem = !isEntitledToNoteLinking && result instanceof SNNote
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
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 <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 hideOnClick
as={Slot}
onClick={() => { onClick={() => {
linkItems(item, result).catch(console.error) void createAndAddNewTag(searchQuery)
combobox.setValue('') combobox.setValue('')
}} }}
> >
<LinkedItemMeta item={result} searchQuery={searchQuery} /> <LinkedItemSearchResultsAddTagOption searchQuery={searchQuery} />
{cannotLinkItem && <Icon type={PremiumFeatureIconName} className="ml-auto flex-shrink-0 text-info" />}
</ComboboxItem> </ComboboxItem>
) )}
})} </ComboboxPopover>
{shouldShowCreateTag && ( </form>
<ComboboxItem </div>
hideOnClick )
as={Slot} },
onClick={() => { )
void createAndAddNewTag(searchQuery)
combobox.setValue('')
}}
>
<LinkedItemSearchResultsAddTagOption searchQuery={searchQuery} />
</ComboboxItem>
)}
</ComboboxPopover>
</form>
</div>
)
}
export default observer(ItemLinkAutocompleteInput) export default observer(ItemLinkAutocompleteInput)

View File

@@ -2,7 +2,7 @@ import { observer } from 'mobx-react-lite'
import ItemLinkAutocompleteInput from './ItemLinkAutocompleteInput' import ItemLinkAutocompleteInput from './ItemLinkAutocompleteInput'
import { LinkingController } from '@/Controllers/LinkingController' import { LinkingController } from '@/Controllers/LinkingController'
import LinkedItemBubble from './LinkedItemBubble' 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 { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { ElementIds } from '@/Constants/ElementIDs' import { ElementIds } from '@/Constants/ElementIDs'
import { classNames } from '@standardnotes/utils' import { classNames } from '@standardnotes/utils'
@@ -112,6 +112,34 @@ const LinkedItemBubblesContainer = ({ item, linkingController, hideToggle = fals
const visibleItems = isCollapsed ? itemsToDisplay.slice(0, 5) : itemsToDisplay const visibleItems = isCollapsed ? itemsToDisplay.slice(0, 5) : itemsToDisplay
const nonVisibleItems = itemsToDisplay.length - visibleItems.length 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 ( return (
<div <div
className={classNames( className={classNames(
@@ -126,6 +154,7 @@ const LinkedItemBubblesContainer = ({ item, linkingController, hideToggle = fals
allItemsLinkedToItem.length || notesLinkingToItem.length ? 'mt-1' : 'mt-0.5', allItemsLinkedToItem.length || notesLinkingToItem.length ? 'mt-1' : 'mt-0.5',
isCollapsed ? 'overflow-hidden' : 'flex-wrap', isCollapsed ? 'overflow-hidden' : 'flex-wrap',
)} )}
ref={linkContainerRef}
> >
{visibleItems.map((link) => ( {visibleItems.map((link) => (
<LinkedItemBubble <LinkedItemBubble
@@ -148,9 +177,10 @@ const LinkedItemBubblesContainer = ({ item, linkingController, hideToggle = fals
setFocusedId={setFocusedId} setFocusedId={setFocusedId}
hoverLabel={`Focus input to add a link (${shortcut})`} hoverLabel={`Focus input to add a link (${shortcut})`}
item={item} item={item}
ref={linkInputRef}
/> />
</div> </div>
{itemsToDisplay.length > 0 && !hideToggle && ( {itemsToDisplay.length > 0 && !shouldHideToggle && (
<RoundIconButton <RoundIconButton
id="toggle-linking-container" id="toggle-linking-container"
label="Toggle linked items container" label="Toggle linked items container"