Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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'
Expand All @@ -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
Expand All @@ -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)
Expand Up @@ -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'
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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"
Expand Down