refactor: clipper ui (#2330)

This commit is contained in:
Aman Harwara
2023-05-10 20:32:43 +05:30
committed by GitHub
parent cd4455e893
commit 1b0e2af2fe
4 changed files with 184 additions and 167 deletions

View File

@@ -1,6 +1,5 @@
import { WebApplicationGroup } from '@/Application/WebApplicationGroup' import { WebApplicationGroup } from '@/Application/WebApplicationGroup'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager' import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { SNLogoFull } from '@standardnotes/icons'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { AccountMenuPane } from '../AccountMenu/AccountMenuPane' import { AccountMenuPane } from '../AccountMenu/AccountMenuPane'
import MenuPaneSelector from '../AccountMenu/MenuPaneSelector' import MenuPaneSelector from '../AccountMenu/MenuPaneSelector'
@@ -38,12 +37,6 @@ import LinkedItemBubble from '../LinkedItems/LinkedItemBubble'
import StyledTooltip from '../StyledTooltip/StyledTooltip' import StyledTooltip from '../StyledTooltip/StyledTooltip'
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem' import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
const Header = () => (
<div className="flex items-center border-b border-border p-1 px-3 py-2 text-base font-semibold text-info-contrast">
<SNLogoFull className="h-7" />
</div>
)
const ClipperView = ({ const ClipperView = ({
viewControllerManager, viewControllerManager,
applicationGroup, applicationGroup,
@@ -257,8 +250,6 @@ const ClipperView = ({
if (user && !isEntitledToExtension) { if (user && !isEntitledToExtension) {
return ( return (
<>
<Header />
<div className="px-3 py-3"> <div className="px-3 py-3">
<div <div
className="mx-auto mb-5 flex h-24 w-24 items-center justify-center rounded-[50%] bg-contrast" className="mx-auto mb-5 flex h-24 w-24 items-center justify-center rounded-[50%] bg-contrast"
@@ -278,14 +269,11 @@ const ClipperView = ({
Sign out Sign out
</Button> </Button>
</div> </div>
</>
) )
} }
if (clippedNote) { if (clippedNote) {
return ( return (
<>
<Header />
<ClippedNoteView <ClippedNoteView
note={clippedNote} note={clippedNote}
key={clippedNote.uuid} key={clippedNote.uuid}
@@ -293,15 +281,11 @@ const ClipperView = ({
clearClip={clearClip} clearClip={clearClip}
isFirefoxPopup={isFirefoxPopup} isFirefoxPopup={isFirefoxPopup}
/> />
</>
) )
} }
if (!user) { if (!user) {
return ( return menuPane ? (
<>
<Header />
{menuPane ? (
<div className="py-1"> <div className="py-1">
<MenuPaneSelector <MenuPaneSelector
viewControllerManager={viewControllerManager} viewControllerManager={viewControllerManager}
@@ -323,16 +307,28 @@ const ClipperView = ({
Sign in Sign in
</MenuItem> </MenuItem>
</Menu> </Menu>
)}
</>
) )
} }
return ( return (
<> <div className="bg-contrast p-3">
<Header /> <Menu a11yLabel="Extension menu" isOpen={true} className="rounded border border-border bg-default">
<div> {hasSelection && (
<Menu a11yLabel="Extension menu" isOpen={true} className="pb-1"> <MenuItem
className="border-b border-border"
disabled={isScreenshotMode}
onClick={async () => {
const payload = await sendMessageToActiveTab({ type: RuntimeMessageTypes.GetSelection })
if (!payload) {
return
}
setClipPayload(payload)
}}
>
<Icon type="paragraph" className="mr-2 text-info" />
Clip text selection
</MenuItem>
)}
<MenuItem <MenuItem
onClick={async () => { onClick={async () => {
const payload = await sendMessageToActiveTab({ type: RuntimeMessageTypes.GetFullPage }) const payload = await sendMessageToActiveTab({ type: RuntimeMessageTypes.GetFullPage })
@@ -342,6 +338,7 @@ const ClipperView = ({
setClipPayload(payload) setClipPayload(payload)
}} }}
> >
<Icon type="notes-filled" className="mr-2 text-info" />
{isScreenshotMode ? 'Capture visible' : 'Clip full page'} {isScreenshotMode ? 'Capture visible' : 'Clip full page'}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
@@ -354,26 +351,16 @@ const ClipperView = ({
setClipPayload(payload) setClipPayload(payload)
}} }}
> >
<Icon type="rich-text" className="mr-2 text-info" />
Clip article Clip article
</MenuItem> </MenuItem>
<MenuItem
disabled={!hasSelection || isScreenshotMode}
onClick={async () => {
const payload = await sendMessageToActiveTab({ type: RuntimeMessageTypes.GetSelection })
if (!payload) {
return
}
setClipPayload(payload)
}}
>
Clip text selection
</MenuItem>
<MenuItem <MenuItem
onClick={async () => { onClick={async () => {
void sendMessageToActiveTab({ type: RuntimeMessageTypes.StartNodeSelection }) void sendMessageToActiveTab({ type: RuntimeMessageTypes.StartNodeSelection })
window.close() window.close()
}} }}
> >
<Icon type="dashboard" className="mr-2 text-info" />
Select elements to {isScreenshotMode ? 'capture' : 'clip'} Select elements to {isScreenshotMode ? 'capture' : 'clip'}
</MenuItem> </MenuItem>
<MenuSwitchButtonItem <MenuSwitchButtonItem
@@ -385,46 +372,49 @@ const ClipperView = ({
> >
Clip as screenshot Clip as screenshot
</MenuSwitchButtonItem> </MenuSwitchButtonItem>
<div className="border-t border-border px-3 py-3 text-base text-foreground"> <div className="border-t border-border px-3 py-3 text-foreground">
<div className="flex items-center justify-between">
<div className="font-medium">Default tag</div>
{defaultTag && ( {defaultTag && (
<StyledTooltip label="Remove default tag" gutter={2}> <div className="flex items-center justify-between text-base">
<button className="rounded-full p-1 hover:bg-contrast hover:text-info" onClick={unselectTag}>
<Icon type="clear-circle-filled" />
</button>
</StyledTooltip>
)}
</div>
{defaultTag && (
<div>
<LinkedItemBubble <LinkedItemBubble
className="m-1 mr-2" className="m-1 mr-2 min-w-0"
link={createLinkFromItem(defaultTag, 'linked')} link={createLinkFromItem(defaultTag, 'linked')}
unlinkItem={unselectTag} unlinkItem={unselectTag}
isBidirectional={false} isBidirectional={false}
inlineFlex={true}
/> />
<StyledTooltip label="Remove default tag" gutter={2}>
<button
className="rounded-full p-1 text-neutral hover:bg-contrast hover:text-info"
onClick={unselectTag}
>
<Icon type="clear-circle-filled" />
</button>
</StyledTooltip>
</div> </div>
)} )}
<ItemSelectionDropdown <ItemSelectionDropdown
onSelection={selectTag} onSelection={selectTag}
placeholder="Select tag to save clipped notes to..." placeholder="Select tag to save clipped notes to..."
contentTypes={[ContentType.Tag]} contentTypes={[ContentType.Tag]}
className={{
input: 'text-[0.85rem]',
}}
comboboxProps={{
placement: 'top',
}}
/> />
</div> </div>
<div className="border-t border-border px-3 pt-3 pb-1 text-base text-foreground"> <div className="flex items-center border-t border-border text-foreground">
<div>You're signed in as:</div> <Icon type="user" className="mx-2" />
<div className="wrap my-0.5 font-bold">{user.email}</div> <div className="flex-grow py-2 text-sm font-semibold">{user.email}</div>
<span className="text-neutral">{application.getHost()}</span> <button
className="flex-shrink-0 border-l border-border py-2 px-2 hover:bg-info-backdrop focus:bg-info-backdrop focus:shadow-none focus:outline-none"
onClick={showSignOutConfirmation}
>
<Icon type="signOut" className="text-neutral" />
</button>
</div> </div>
<MenuItem onClick={showSignOutConfirmation}>
<Icon type="signOut" className="mr-2 h-6 w-6 text-neutral" />
Sign out
</MenuItem>
</Menu> </Menu>
</div> </div>
</>
) )
} }

View File

@@ -1,6 +1,13 @@
import { doesItemMatchSearchQuery } from '@/Utils/Items/Search/doesItemMatchSearchQuery' import { doesItemMatchSearchQuery } from '@/Utils/Items/Search/doesItemMatchSearchQuery'
import { Combobox, ComboboxItem, ComboboxPopover, useComboboxStore, VisuallyHidden } from '@ariakit/react' import {
import { ContentType, DecryptedItem, naturalSort } from '@standardnotes/snjs' Combobox,
ComboboxItem,
ComboboxPopover,
ComboboxStoreProps,
useComboboxStore,
VisuallyHidden,
} from '@ariakit/react'
import { classNames, ContentType, DecryptedItem, naturalSort } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { useDeferredValue, useEffect, useState } from 'react' import { useDeferredValue, useEffect, useState } from 'react'
import { useApplication } from '../ApplicationProvider' import { useApplication } from '../ApplicationProvider'
@@ -10,13 +17,23 @@ type Props = {
contentTypes: ContentType[] contentTypes: ContentType[]
placeholder: string placeholder: string
onSelection: (item: DecryptedItem) => void onSelection: (item: DecryptedItem) => void
className?: {
input?: string
popover?: string
}
comboboxProps?: ComboboxStoreProps
} }
const ItemSelectionDropdown = ({ contentTypes, placeholder, onSelection }: Props) => { const ItemSelectionDropdown = ({ contentTypes, placeholder, onSelection, comboboxProps, className = {} }: Props) => {
const application = useApplication() const application = useApplication()
const combobox = useComboboxStore() const combobox = useComboboxStore(comboboxProps)
const value = combobox.useState('value') const value = combobox.useState('value')
const open = combobox.useState('open')
if (value.length < 1 && open) {
combobox.setOpen(false)
}
const searchQuery = useDeferredValue(value) const searchQuery = useDeferredValue(value)
const [items, setItems] = useState<DecryptedItem[]>([]) const [items, setItems] = useState<DecryptedItem[]>([])
@@ -30,17 +47,21 @@ const ItemSelectionDropdown = ({ contentTypes, placeholder, onSelection }: Props
return ( return (
<div> <div>
<label>
<VisuallyHidden>Select an item</VisuallyHidden> <VisuallyHidden>Select an item</VisuallyHidden>
<Combobox <Combobox
store={combobox} store={combobox}
placeholder={placeholder} placeholder={placeholder}
className="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" className={classNames(
'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',
className.input,
)}
/> />
</label>
<ComboboxPopover <ComboboxPopover
store={combobox} store={combobox}
className="z-dropdown-menu max-h-[var(--popover-available-height)] w-[var(--popover-anchor-width)] overflow-y-auto rounded bg-default py-2 shadow-main" 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',
className.popover,
)}
> >
{items.length > 0 ? ( {items.length > 0 ? (
items.map((item) => ( items.map((item) => (

View File

@@ -41,7 +41,13 @@ const MenuSwitchButtonItem = forwardRef(
<span className="flex flex-grow items-center">{children}</span> <span className="flex flex-grow items-center">{children}</span>
<div className="flex items-center"> <div className="flex items-center">
{shortcut && <KeyboardShortcutIndicator className="mr-2" shortcut={shortcut} />} {shortcut && <KeyboardShortcutIndicator className="mr-2" shortcut={shortcut} />}
<Switch disabled={disabled} className="pointer-events-none px-0" checked={checked} onChange={onChange} /> <Switch
disabled={disabled}
className="pointer-events-none px-0"
checked={checked}
onChange={onChange}
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
/>
</div> </div>
</button> </button>
</MenuListItem> </MenuListItem>

View File

@@ -96,16 +96,16 @@ export const useListKeyboardNavigation = (
const selectedItemIndex = Array.from(items).findIndex((item) => item.dataset.selected) const selectedItemIndex = Array.from(items).findIndex((item) => item.dataset.selected)
let indexToFocus = selectedItemIndex > -1 ? selectedItemIndex : initialFocus let indexToFocus = selectedItemIndex > -1 ? selectedItemIndex : initialFocus
indexToFocus = getNextFocusableIndex(indexToFocus, items) indexToFocus = getNextFocusableIndex(indexToFocus - 1, items)
setTimeout(() => {
focusItemWithIndex(indexToFocus, items) focusItemWithIndex(indexToFocus, items)
}, FIRST_ITEM_FOCUS_TIMEOUT)
}, [container, focusItemWithIndex, getNextFocusableIndex, initialFocus]) }, [container, focusItemWithIndex, getNextFocusableIndex, initialFocus])
useEffect(() => { useEffect(() => {
if (shouldAutoFocus) { if (shouldAutoFocus) {
setTimeout(() => {
setInitialFocus() setInitialFocus()
}, FIRST_ITEM_FOCUS_TIMEOUT)
} }
}, [setInitialFocus, shouldAutoFocus]) }, [setInitialFocus, shouldAutoFocus])