diff --git a/.yarn/cache/@reach-auto-id-npm-0.18.0-f919ca8edd-75a37a0a09.zip b/.yarn/cache/@reach-auto-id-npm-0.18.0-f919ca8edd-75a37a0a09.zip deleted file mode 100644 index de667c0cf..000000000 Binary files a/.yarn/cache/@reach-auto-id-npm-0.18.0-f919ca8edd-75a37a0a09.zip and /dev/null differ diff --git a/.yarn/cache/@reach-disclosure-npm-0.18.0-2fdc238043-7786674320.zip b/.yarn/cache/@reach-disclosure-npm-0.18.0-2fdc238043-7786674320.zip deleted file mode 100644 index ff414e812..000000000 Binary files a/.yarn/cache/@reach-disclosure-npm-0.18.0-2fdc238043-7786674320.zip and /dev/null differ diff --git a/.yarn/cache/@reach-polymorphic-npm-0.18.0-31b4a0e8fe-0d62260a55.zip b/.yarn/cache/@reach-polymorphic-npm-0.18.0-31b4a0e8fe-0d62260a55.zip deleted file mode 100644 index 1408f4fb5..000000000 Binary files a/.yarn/cache/@reach-polymorphic-npm-0.18.0-31b4a0e8fe-0d62260a55.zip and /dev/null differ diff --git a/.yarn/cache/@reach-utils-npm-0.18.0-a458ed585d-eeda20a74c.zip b/.yarn/cache/@reach-utils-npm-0.18.0-a458ed585d-eeda20a74c.zip deleted file mode 100644 index 118bd8497..000000000 Binary files a/.yarn/cache/@reach-utils-npm-0.18.0-a458ed585d-eeda20a74c.zip and /dev/null differ diff --git a/packages/web/package.json b/packages/web/package.json index 4f29bb007..ef1f5c3bd 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -30,7 +30,6 @@ "@babel/preset-typescript": "^7.18.6", "@lexical/react": "0.9.2", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", - "@reach/disclosure": "^0.18.0", "@simplewebauthn/browser": "^7.1.0", "@standardnotes/authenticator": "^2.3.9", "@standardnotes/autobiography-theme": "^1.2.7", diff --git a/packages/web/src/javascripts/Components/ItemSelectionDropdown/ItemSelectionDropdown.tsx b/packages/web/src/javascripts/Components/ItemSelectionDropdown/ItemSelectionDropdown.tsx index cbe550390..56058b922 100644 --- a/packages/web/src/javascripts/Components/ItemSelectionDropdown/ItemSelectionDropdown.tsx +++ b/packages/web/src/javascripts/Components/ItemSelectionDropdown/ItemSelectionDropdown.tsx @@ -1,13 +1,10 @@ -import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants' -import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' import { doesItemMatchSearchQuery } from '@/Utils/Items/Search/doesItemMatchSearchQuery' -import { Disclosure, DisclosurePanel } from '@reach/disclosure' -import { classNames, ContentType, DecryptedItem, naturalSort } from '@standardnotes/snjs' +import { Combobox, ComboboxItem, ComboboxPopover, useComboboxStore, VisuallyHidden } from '@ariakit/react' +import { ContentType, DecryptedItem, naturalSort } from '@standardnotes/snjs' import { observer } from 'mobx-react-lite' -import { ChangeEventHandler, FocusEventHandler, useCallback, useEffect, useRef, useState } from 'react' +import { useDeferredValue, useEffect, useState } from 'react' import { useApplication } from '../ApplicationProvider' import LinkedItemMeta from '../LinkedItems/LinkedItemMeta' -import Menu from '../Menu/Menu' type Props = { contentTypes: ContentType[] @@ -18,41 +15,11 @@ type Props = { const ItemSelectionDropdown = ({ contentTypes, placeholder, onSelection }: Props) => { const application = useApplication() - const [searchQuery, setSearchQuery] = useState('') - const [dropdownVisible, setDropdownVisible] = useState(false) - - const [dropdownMaxHeight, setDropdownMaxHeight] = useState('auto') - const containerRef = useRef(null) - const inputRef = useRef(null) - const searchResultsMenuRef = useRef(null) + const combobox = useComboboxStore() + const value = combobox.useState('value') + const searchQuery = useDeferredValue(value) const [items, setItems] = useState([]) - const showDropdown = () => { - const { clientHeight } = document.documentElement - const inputRect = inputRef.current?.getBoundingClientRect() - if (inputRect) { - setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2) - setDropdownVisible(true) - } - } - - const [closeOnBlur] = useCloseOnBlur(containerRef, (visible: boolean) => { - setDropdownVisible(visible) - setSearchQuery('') - }) - - const onBlur: FocusEventHandler = (event) => { - closeOnBlur(event) - } - - const onSearchQueryChange: ChangeEventHandler = (event) => { - setSearchQuery(event.currentTarget.value) - } - - const handleFocus = () => { - showDropdown() - } - useEffect(() => { const searchableItems = naturalSort(application.items.getItems(contentTypes), 'title') const filteredItems = searchableItems.filter((item) => { @@ -61,70 +28,38 @@ const ItemSelectionDropdown = ({ contentTypes, placeholder, onSelection }: Props setItems(filteredItems) }, [searchQuery, application, contentTypes]) - const onSelectItem = useCallback( - (item: DecryptedItem) => { - onSelection(item) - setSearchQuery('') - setDropdownVisible(false) - }, - [onSelection], - ) - return ( -
- - + +
) } diff --git a/packages/web/src/javascripts/Components/LinkedItems/ItemLinkAutocompleteInput.tsx b/packages/web/src/javascripts/Components/LinkedItems/ItemLinkAutocompleteInput.tsx index d7a0e2eae..73472ec40 100644 --- a/packages/web/src/javascripts/Components/LinkedItems/ItemLinkAutocompleteInput.tsx +++ b/packages/web/src/javascripts/Components/LinkedItems/ItemLinkAutocompleteInput.tsx @@ -1,26 +1,18 @@ -import { - ChangeEventHandler, - FocusEventHandler, - FormEventHandler, - KeyboardEventHandler, - useCallback, - useEffect, - useRef, - useState, -} from 'react' -import { Disclosure, DisclosurePanel } from '@reach/disclosure' -import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' +import { FormEventHandler, KeyboardEventHandler, useDeferredValue, useEffect, useRef } from 'react' import { observer } from 'mobx-react-lite' import { classNames } from '@standardnotes/utils' -import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants' -import LinkedItemSearchResults from './LinkedItemSearchResults' import { LinkingController } from '@/Controllers/LinkingController' -import { KeyboardKey } from '@standardnotes/ui-services' import { ElementIds } from '@/Constants/ElementIDs' -import Menu from '../Menu/Menu' import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults' import { useApplication } from '../ApplicationProvider' -import { DecryptedItem } from '@standardnotes/snjs' +import { DecryptedItem, SNNote } from '@standardnotes/snjs' +import { Combobox, ComboboxItem, ComboboxPopover, useComboboxStore, VisuallyHidden } from '@ariakit/react' +import LinkedItemMeta from './LinkedItemMeta' +import { LinkedItemSearchResultsAddTagOption } from './LinkedItemSearchResultsAddTagOption' +import { Slot } from '@radix-ui/react-slot' +import Icon from '../Icon/Icon' +import { PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon' +import { KeyboardKey } from '@standardnotes/ui-services' type Props = { linkingController: LinkingController @@ -45,33 +37,13 @@ const ItemLinkAutocompleteInput = ({ const tagsLinkedToItem = getLinkedTagsForItem(item) || [] - const [searchQuery, setSearchQuery] = useState('') + const combobox = useComboboxStore() + const value = combobox.useState('value') + const searchQuery = useDeferredValue(value) + const { unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, item) - const [dropdownVisible, setDropdownVisible] = useState(false) - const [dropdownMaxHeight, setDropdownMaxHeight] = useState('auto') - - const containerRef = useRef(null) - const inputRef = useRef(null) - const searchResultsMenuRef = useRef(null) - - const [closeOnBlur] = useCloseOnBlur(containerRef, (visible: boolean) => { - setDropdownVisible(visible) - setSearchQuery('') - }) - - const showDropdown = () => { - const { clientHeight } = document.documentElement - const inputRect = inputRef.current?.getBoundingClientRect() - if (inputRect) { - setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2) - setDropdownVisible(true) - } - } - - const onSearchQueryChange: ChangeEventHandler = (event) => { - setSearchQuery(event.currentTarget.value) - } + const inputRef = useRef(null) const onFormSubmit: FormEventHandler = async (event) => { event.preventDefault() @@ -84,11 +56,6 @@ const ItemLinkAutocompleteInput = ({ if (focusedId !== ElementIds.ItemLinkAutocompleteInput) { setFocusedId(ElementIds.ItemLinkAutocompleteInput) } - showDropdown() - } - - const onBlur: FocusEventHandler = (event) => { - closeOnBlur(event) } const onKeyDown: KeyboardEventHandler = (event) => { @@ -98,12 +65,6 @@ const ItemLinkAutocompleteInput = ({ focusPreviousItem() } break - case KeyboardKey.Down: - if (searchQuery.length > 0) { - event.preventDefault() - searchResultsMenuRef.current?.focus() - } - break } } @@ -113,70 +74,63 @@ const ItemLinkAutocompleteInput = ({ } }, [focusedId]) - const areSearchResultsVisible = dropdownVisible && (unlinkedItems.length > 0 || shouldShowCreateTag) - - const handleMenuKeyDown: KeyboardEventHandler = useCallback((event) => { - if (event.key === KeyboardKey.Escape) { - inputRef.current?.focus() - } - }, []) - return ( -
+
- - + Link tags, notes or files + 0 ? 'w-80' : 'mr-10 w-70'}`, - 'bg-transparent text-sm text-text focus:border-b-2 focus:border-solid focus:border-info lg:text-xs', - 'no-border h-7 focus:shadow-none focus:outline-none', + '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', )} - value={searchQuery} - onChange={onSearchQueryChange} - type="text" - placeholder="Link tags, notes, files..." - onBlur={onBlur} + title={hoverLabel} + id={ElementIds.ItemLinkAutocompleteInput} + ref={inputRef} onFocus={handleFocus} onKeyDown={onKeyDown} - id={ElementIds.ItemLinkAutocompleteInput} - autoComplete="off" - title={hoverLabel} - aria-label={hoverLabel} /> - {areSearchResultsVisible && ( - 0 ? 'w-80' : 'mr-10 w-70', - 'absolute z-dropdown-menu flex flex-col overflow-y-auto rounded bg-default py-2 shadow-main', - )} - style={{ - maxHeight: dropdownMaxHeight, - }} - onBlur={closeOnBlur} - tabIndex={FOCUSABLE_BUT_NOT_TABBABLE} - > - - setSearchQuery('')} - isEntitledToNoteLinking={isEntitledToNoteLinking} - /> - - + + + > + {unlinkedItems.map((result) => { + const cannotLinkItem = !isEntitledToNoteLinking && result instanceof SNNote + + return ( + { + linkItems(item, result).catch(console.error) + combobox.setValue('') + }} + > + + {cannotLinkItem && } + + ) + })} + {shouldShowCreateTag && ( + { + void createAndAddNewTag(searchQuery) + combobox.setValue('') + }} + > + + + )} +
) diff --git a/packages/web/src/javascripts/Components/LinkedItems/LinkedItemSearchResultsAddTagOption.tsx b/packages/web/src/javascripts/Components/LinkedItems/LinkedItemSearchResultsAddTagOption.tsx index 3d4d7a58b..76b8bc0ae 100644 --- a/packages/web/src/javascripts/Components/LinkedItems/LinkedItemSearchResultsAddTagOption.tsx +++ b/packages/web/src/javascripts/Components/LinkedItems/LinkedItemSearchResultsAddTagOption.tsx @@ -1,42 +1,49 @@ import { classNames } from '@standardnotes/utils' +import { ComponentPropsWithoutRef, ForwardedRef, forwardRef } from 'react' import Icon from '../Icon/Icon' type Props = { searchQuery: string - onClickCallback: (searchQuery: string) => void + onClickCallback?: (searchQuery: string) => void isFocused?: boolean -} +} & ComponentPropsWithoutRef<'button'> -export const LinkedItemSearchResultsAddTagOption = ({ searchQuery, onClickCallback, isFocused }: Props) => { - return ( - - ) -} + > + + {searchQuery} + + + ) + }, +) diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Security/TwoFactorAuth/AuthAppInfoPopup.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Security/TwoFactorAuth/AuthAppInfoPopup.tsx index fd369758b..8d9132593 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Security/TwoFactorAuth/AuthAppInfoPopup.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Security/TwoFactorAuth/AuthAppInfoPopup.tsx @@ -1,65 +1,21 @@ import Icon from '@/Components/Icon/Icon' -import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure' -import { FunctionComponent, useState, useRef, useEffect, MouseEventHandler } from 'react' -import { IconType } from '@standardnotes/snjs' +import { Hovercard, HovercardAnchor, useHovercardStore } from '@ariakit/react' -type Props = { - className?: string - icon: IconType - onMouseEnter?: MouseEventHandler - onMouseLeave?: MouseEventHandler -} - -const DisclosureIconButton: FunctionComponent = ({ className = '', icon, onMouseEnter, onMouseLeave }) => ( - - - -) - -/** - * AuthAppInfoPopup is an info icon that shows a tooltip when clicked - * Tooltip is dismissible by clicking outside - * - * Note: it can be generalized but more use cases are required - * @returns - */ -const AuthAppInfoTooltip: FunctionComponent = () => { - const [isClicked, setClicked] = useState(false) - const [isHover, setHover] = useState(false) - const ref = useRef(null) - - useEffect(() => { - const dismiss = () => setClicked(false) - document.addEventListener('mousedown', dismiss) - return () => { - document.removeEventListener('mousedown', dismiss) - } - }, [ref]) +const AuthAppInfoTooltip = () => { + const infoHovercard = useHovercardStore({ + showTimeout: 100, + }) return ( - setClicked(!isClicked)}> -
- setHover(true)} - onMouseLeave={() => setHover(false)} - /> - -
- Some apps, like Google Authenticator, do not back up and restore your secret keys if you lose your device or - get a new one. -
-
-
-
+ <> + + + + + Some apps, like Google Authenticator, do not back up and restore your secret keys if you lose your device or get + a new one. + + ) } diff --git a/packages/web/src/javascripts/Components/Preferences/PreferencesMenuView.tsx b/packages/web/src/javascripts/Components/Preferences/PreferencesMenuView.tsx index ddf809f97..2f52f5be0 100644 --- a/packages/web/src/javascripts/Components/Preferences/PreferencesMenuView.tsx +++ b/packages/web/src/javascripts/Components/Preferences/PreferencesMenuView.tsx @@ -78,7 +78,6 @@ const PreferencesMenuView: FunctionComponent = ({ menu }) => { }} classNameOverride={{ wrapper: 'relative', - popover: 'bottom-full w-full max-h-max', button: 'focus:outline-none focus:shadow-none focus:ring-none', }} /> diff --git a/packages/web/src/javascripts/Components/Shared/AccordionItem.tsx b/packages/web/src/javascripts/Components/Shared/AccordionItem.tsx index 9a43cb78b..65ac6eb81 100644 --- a/packages/web/src/javascripts/Components/Shared/AccordionItem.tsx +++ b/packages/web/src/javascripts/Components/Shared/AccordionItem.tsx @@ -15,14 +15,14 @@ const AccordionItem: FunctionComponent = ({ title, className = '', childr return (
{ setIsExpanded(!isExpanded) }} > {title} { const [shouldShowIconPicker, setShouldShowIconPicker] = useState(false) const iconPickerButtonRef = useRef(null) - const [shouldShowJsonExamples, setShouldShowJsonExamples] = useState(false) + const jsonExamplesDisclosure = useDisclosureStore() + const showingJsonExamples = jsonExamplesDisclosure.useState('open') const toggleIconPicker = () => { setShouldShowIconPicker((shouldShow) => !shouldShow) @@ -223,22 +224,26 @@ const AddSmartViewModal = ({ controller, platform }: Props) => { {tabState.activeTab === 'custom' && ( - setShouldShowJsonExamples((show) => !show)}> -
- -
Examples
- -
- -
1. List notes that are conflicted copies of another note:
- -
- 2. List notes that have the tag `todo` but not the tag `completed`: -
- -
-
-
+
+ +
Examples
+ +
+ +
1. List notes that are conflicted copies of another note:
+ +
+ 2. List notes that have the tag `todo` but not the tag `completed`: +
+ +
+
)}
diff --git a/yarn.lock b/yarn.lock index f93933713..806905cf7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4230,51 +4230,6 @@ __metadata: languageName: node linkType: hard -"@reach/auto-id@npm:0.18.0": - version: 0.18.0 - resolution: "@reach/auto-id@npm:0.18.0" - dependencies: - "@reach/utils": 0.18.0 - peerDependencies: - react: ^16.8.0 || 17.x - react-dom: ^16.8.0 || 17.x - checksum: 75a37a0a09c382dfc358d37f3212cffdea2b4c80c2f555b7fee857af59a46e959324234be8dbb51c476fba0874ed93694254079192b478ce3e9abba5ce63fdec - languageName: node - linkType: hard - -"@reach/disclosure@npm:^0.18.0": - version: 0.18.0 - resolution: "@reach/disclosure@npm:0.18.0" - dependencies: - "@reach/auto-id": 0.18.0 - "@reach/polymorphic": 0.18.0 - "@reach/utils": 0.18.0 - peerDependencies: - react: ^16.8.0 || 17.x - react-dom: ^16.8.0 || 17.x - checksum: 77866743202a87e1c608fb41a56180b6676894a71b9853a6ca45408c82962f85fb7c3405e10c296518fcba95b0e7a4848ca681a6d8beb6d4574d548e606316d9 - languageName: node - linkType: hard - -"@reach/polymorphic@npm:0.18.0": - version: 0.18.0 - resolution: "@reach/polymorphic@npm:0.18.0" - peerDependencies: - react: ^16.8.0 || 17.x - checksum: 0d62260a55c71e0dc95f38867f24b3d699e0bb7ea2273c6fa5a1fa3d804269e2deb63a458f1f1eb7b74c32bc0add3ab31a1c2668c1dee6b2837187876d3c4cc2 - languageName: node - linkType: hard - -"@reach/utils@npm:0.18.0": - version: 0.18.0 - resolution: "@reach/utils@npm:0.18.0" - peerDependencies: - react: ^16.8.0 || 17.x - react-dom: ^16.8.0 || 17.x - checksum: eeda20a74c1db71e95680c622371c6efc46e2c767b28902bc43b580654436a34ec7f8b5f63c51a7c4cc8fd48c94066e3215496ba5a053c96e65c7a9bdcdc2687 - languageName: node - linkType: hard - "@react-native-async-storage/async-storage@npm:1.17.11": version: 1.17.11 resolution: "@react-native-async-storage/async-storage@npm:1.17.11" @@ -5558,7 +5513,6 @@ __metadata: "@lexical/react": 0.9.2 "@pmmmwh/react-refresh-webpack-plugin": ^0.5.10 "@radix-ui/react-slot": ^1.0.1 - "@reach/disclosure": ^0.18.0 "@simplewebauthn/browser": ^7.1.0 "@standardnotes/authenticator": ^2.3.9 "@standardnotes/autobiography-theme": ^1.2.7