fix: notes list options menu not toggling correctly (#840)

This commit is contained in:
Aman Harwara
2022-02-01 20:12:57 +05:30
committed by GitHub
parent 6150f58c73
commit 1f5d235cd4
5 changed files with 93 additions and 51 deletions

View File

@@ -6,19 +6,19 @@ import { useRef, useState } from 'preact/hooks';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { Menu } from './menu/Menu'; import { Menu } from './menu/Menu';
import { MenuItem, MenuItemSeparator, MenuItemType } from './menu/MenuItem'; import { MenuItem, MenuItemSeparator, MenuItemType } from './menu/MenuItem';
import { useCloseOnClickOutside } from './utils';
type Props = { type Props = {
application: WebApplication; application: WebApplication;
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void;
closeDisplayOptionsMenu: () => void; closeDisplayOptionsMenu: () => void;
}; };
export const NotesListOptionsMenu: FunctionComponent<Props> = observer( export const NotesListOptionsMenu: FunctionComponent<Props> = observer(
({ closeDisplayOptionsMenu, application }) => { ({ closeDisplayOptionsMenu, closeOnBlur, application }) => {
const menuClassName = const menuClassName =
'sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \ 'sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \
border-1 border-solid border-main text-sm z-index-dropdown-menu \ border-1 border-solid border-main text-sm z-index-dropdown-menu \
flex flex-col py-2 bottom-0 left-2 absolute'; flex flex-col py-2 top-full bottom-0 left-2 absolute';
const [sortBy, setSortBy] = useState(() => const [sortBy, setSortBy] = useState(() =>
application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt) application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt)
); );
@@ -118,10 +118,6 @@ flex flex-col py-2 bottom-0 left-2 absolute';
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
useCloseOnClickOutside(menuRef, () => {
closeDisplayOptionsMenu();
});
return ( return (
<div ref={menuRef} className={menuClassName}> <div ref={menuRef} className={menuClassName}>
<Menu a11yLabel="Sort by" closeMenu={closeDisplayOptionsMenu}> <Menu a11yLabel="Sort by" closeMenu={closeDisplayOptionsMenu}>
@@ -133,6 +129,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
type={MenuItemType.RadioButton} type={MenuItemType.RadioButton}
onClick={toggleSortByDateModified} onClick={toggleSortByDateModified}
checked={sortBy === CollectionSort.UpdatedAt} checked={sortBy === CollectionSort.UpdatedAt}
onBlur={closeOnBlur}
> >
<div className="flex flex-grow items-center justify-between"> <div className="flex flex-grow items-center justify-between">
<span>Date modified</span> <span>Date modified</span>
@@ -150,6 +147,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
type={MenuItemType.RadioButton} type={MenuItemType.RadioButton}
onClick={toggleSortByCreationDate} onClick={toggleSortByCreationDate}
checked={sortBy === CollectionSort.CreatedAt} checked={sortBy === CollectionSort.CreatedAt}
onBlur={closeOnBlur}
> >
<div className="flex flex-grow items-center justify-between"> <div className="flex flex-grow items-center justify-between">
<span>Creation date</span> <span>Creation date</span>
@@ -167,6 +165,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
type={MenuItemType.RadioButton} type={MenuItemType.RadioButton}
onClick={toggleSortByTitle} onClick={toggleSortByTitle}
checked={sortBy === CollectionSort.Title} checked={sortBy === CollectionSort.Title}
onBlur={closeOnBlur}
> >
<div className="flex flex-grow items-center justify-between"> <div className="flex flex-grow items-center justify-between">
<span>Title</span> <span>Title</span>
@@ -188,6 +187,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hidePreview} checked={!hidePreview}
onChange={toggleHidePreview} onChange={toggleHidePreview}
onBlur={closeOnBlur}
> >
<div className="flex flex-col max-w-3/4">Show note preview</div> <div className="flex flex-col max-w-3/4">Show note preview</div>
</MenuItem> </MenuItem>
@@ -196,6 +196,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideDate} checked={!hideDate}
onChange={toggleHideDate} onChange={toggleHideDate}
onBlur={closeOnBlur}
> >
Show date Show date
</MenuItem> </MenuItem>
@@ -204,6 +205,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideTags} checked={!hideTags}
onChange={toggleHideTags} onChange={toggleHideTags}
onBlur={closeOnBlur}
> >
Show tags Show tags
</MenuItem> </MenuItem>
@@ -212,6 +214,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideEditorIcon} checked={!hideEditorIcon}
onChange={toggleEditorIcon} onChange={toggleEditorIcon}
onBlur={closeOnBlur}
> >
Show editor icon Show editor icon
</MenuItem> </MenuItem>
@@ -224,6 +227,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hidePinned} checked={!hidePinned}
onChange={toggleHidePinned} onChange={toggleHidePinned}
onBlur={closeOnBlur}
> >
Show pinned notes Show pinned notes
</MenuItem> </MenuItem>
@@ -232,6 +236,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideProtected} checked={!hideProtected}
onChange={toggleHideProtected} onChange={toggleHideProtected}
onBlur={closeOnBlur}
> >
Show protected notes Show protected notes
</MenuItem> </MenuItem>
@@ -240,6 +245,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={showArchived} checked={showArchived}
onChange={toggleShowArchived} onChange={toggleShowArchived}
onBlur={closeOnBlur}
> >
Show archived notes Show archived notes
</MenuItem> </MenuItem>
@@ -248,6 +254,7 @@ flex flex-col py-2 bottom-0 left-2 absolute';
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={showTrashed} checked={showTrashed}
onChange={toggleShowTrashed} onChange={toggleShowTrashed}
onBlur={closeOnBlur}
> >
Show trashed notes Show trashed notes
</MenuItem> </MenuItem>

View File

@@ -5,7 +5,7 @@ import { PANEL_NAME_NOTES } from '@/views/constants';
import { PrefKey } from '@standardnotes/snjs'; import { PrefKey } from '@standardnotes/snjs';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact'; import { FunctionComponent } from 'preact';
import { useEffect, useRef } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import { NoAccountWarning } from './NoAccountWarning'; import { NoAccountWarning } from './NoAccountWarning';
import { NotesList } from './NotesList'; import { NotesList } from './NotesList';
import { NotesListOptionsMenu } from './NotesListOptionsMenu'; import { NotesListOptionsMenu } from './NotesListOptionsMenu';
@@ -16,6 +16,12 @@ import {
PanelResizer, PanelResizer,
PanelResizeType, PanelResizeType,
} from './PanelResizer'; } from './PanelResizer';
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from '@reach/disclosure';
import { useCloseOnBlur } from './utils';
type Props = { type Props = {
application: WebApplication; application: WebApplication;
@@ -25,6 +31,7 @@ type Props = {
export const NotesView: FunctionComponent<Props> = observer( export const NotesView: FunctionComponent<Props> = observer(
({ application, appState }) => { ({ application, appState }) => {
const notesViewPanelRef = useRef<HTMLDivElement>(null); const notesViewPanelRef = useRef<HTMLDivElement>(null);
const displayOptionsMenuRef = useRef<HTMLDivElement>(null);
const { const {
completedFullSync, completedFullSync,
@@ -36,8 +43,6 @@ export const NotesView: FunctionComponent<Props> = observer(
renderedNotes, renderedNotes,
selectedNotes, selectedNotes,
setNoteFilterText, setNoteFilterText,
showDisplayOptionsMenu,
toggleDisplayOptionsMenu,
searchBarElement, searchBarElement,
selectNextNote, selectNextNote,
selectPreviousNote, selectPreviousNote,
@@ -49,6 +54,13 @@ export const NotesView: FunctionComponent<Props> = observer(
panelWidth, panelWidth,
} = appState.notesView; } = appState.notesView;
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false);
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(
displayOptionsMenuRef,
setShowDisplayOptionsMenu
);
useEffect(() => { useEffect(() => {
handleFilterTextChanged(); handleFilterTextChanged();
}, [noteFilterText, handleFilterTextChanged]); }, [noteFilterText, handleFilterTextChanged]);
@@ -139,6 +151,10 @@ export const NotesView: FunctionComponent<Props> = observer(
appState.noteTags.reloadTagsContainerMaxWidth(); appState.noteTags.reloadTagsContainerMaxWidth();
}; };
const toggleDisplayOptionsMenu = () => {
setShowDisplayOptionsMenu(!showDisplayOptionsMenu);
};
return ( return (
<div <div
id="notes-column" id="notes-column"
@@ -192,34 +208,42 @@ export const NotesView: FunctionComponent<Props> = observer(
</div> </div>
<NoAccountWarning appState={appState} /> <NoAccountWarning appState={appState} />
</div> </div>
<div id="notes-menu-bar" className="sn-component"> <div
id="notes-menu-bar"
className="sn-component"
ref={displayOptionsMenuRef}
>
<div className="sk-app-bar no-edges"> <div className="sk-app-bar no-edges">
<div className="left"> <div className="left">
<div <Disclosure
className={`sk-app-bar-item ${ open={showDisplayOptionsMenu}
showDisplayOptionsMenu ? 'selected' : '' onChange={toggleDisplayOptionsMenu}
}`}
onClick={() =>
toggleDisplayOptionsMenu(!showDisplayOptionsMenu)
}
> >
<div className="sk-app-bar-item-column"> <DisclosureButton
<div className="sk-label">Options</div> className={`sk-app-bar-item bg-contrast border-0 focus:shadow-none ${
</div> showDisplayOptionsMenu ? 'selected' : ''
<div className="sk-app-bar-item-column"> }`}
<div className="sk-sublabel">{optionsSubtitle}</div> onBlur={closeDisplayOptMenuOnBlur}
</div> >
</div> <div className="sk-app-bar-item-column">
<div className="sk-label">Options</div>
</div>
<div className="sk-app-bar-item-column">
<div className="sk-sublabel">{optionsSubtitle}</div>
</div>
</DisclosureButton>
<DisclosurePanel onBlur={closeDisplayOptMenuOnBlur}>
{showDisplayOptionsMenu && (
<NotesListOptionsMenu
application={application}
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
closeOnBlur={closeDisplayOptMenuOnBlur}
/>
)}
</DisclosurePanel>
</Disclosure>
</div> </div>
</div> </div>
{showDisplayOptionsMenu && (
<NotesListOptionsMenu
application={application}
closeDisplayOptionsMenu={() =>
toggleDisplayOptionsMenu(false)
}
/>
)}
</div> </div>
</div> </div>
{completedFullSync && !renderedNotes.length ? ( {completedFullSync && !renderedNotes.length ? (

View File

@@ -1,14 +1,10 @@
import { import { ComponentChildren, FunctionComponent, VNode } from 'preact';
ComponentChild,
ComponentChildren,
FunctionComponent,
VNode,
} from 'preact';
import { forwardRef, Ref } from 'preact/compat'; import { forwardRef, Ref } from 'preact/compat';
import { JSXInternal } from 'preact/src/jsx'; import { JSXInternal } from 'preact/src/jsx';
import { Icon } from '../Icon'; import { Icon } from '../Icon';
import { Switch, SwitchProps } from '../Switch'; import { Switch, SwitchProps } from '../Switch';
import { IconType } from '@standardnotes/snjs'; import { IconType } from '@standardnotes/snjs';
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/views/constants';
export enum MenuItemType { export enum MenuItemType {
IconButton, IconButton,
@@ -21,6 +17,7 @@ type MenuItemProps = {
children: ComponentChildren; children: ComponentChildren;
onClick?: JSXInternal.MouseEventHandler<HTMLButtonElement>; onClick?: JSXInternal.MouseEventHandler<HTMLButtonElement>;
onChange?: SwitchProps['onChange']; onChange?: SwitchProps['onChange'];
onBlur?: (event: { relatedTarget: EventTarget | null }) => void;
className?: string; className?: string;
checked?: boolean; checked?: boolean;
icon?: IconType; icon?: IconType;
@@ -34,6 +31,7 @@ export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
children, children,
onClick, onClick,
onChange, onChange,
onBlur,
className = '', className = '',
type, type,
checked, checked,
@@ -45,22 +43,31 @@ export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
) => { ) => {
return type === MenuItemType.SwitchButton && return type === MenuItemType.SwitchButton &&
typeof onChange === 'function' ? ( typeof onChange === 'function' ? (
<Switch <button
className="py-1 hover:bg-contrast focus:bg-info-backdrop" className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none justify-between"
checked={checked} onClick={() => {
onChange={onChange} onChange(!checked);
}}
onBlur={onBlur}
tabIndex={
typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE
}
role="menuitemcheckbox" role="menuitemcheckbox"
tabIndex={typeof tabIndex === 'number' ? tabIndex : -1} aria-checked={checked}
> >
{children} <span className="flex flex-grow items-center">{children}</span>
</Switch> <Switch className="px-0" checked={checked} />
</button>
) : ( ) : (
<button <button
ref={ref} ref={ref}
role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'} role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'}
tabIndex={typeof tabIndex === 'number' ? tabIndex : -1} tabIndex={
typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE
}
className={`sn-dropdown-item focus:bg-info-backdrop focus:shadow-none ${className}`} className={`sn-dropdown-item focus:bg-info-backdrop focus:shadow-none ${className}`}
onClick={onClick} onClick={onClick}
onBlur={onBlur}
{...(type === MenuItemType.RadioButton {...(type === MenuItemType.RadioButton
? { 'aria-checked': checked } ? { 'aria-checked': checked }
: {})} : {})}

View File

@@ -146,7 +146,7 @@ export class NotesViewState {
} else if (eventName === AppStateEvent.ActiveEditorChanged) { } else if (eventName === AppStateEvent.ActiveEditorChanged) {
this.handleEditorChange(); this.handleEditorChange();
} else if (eventName === AppStateEvent.EditorFocused) { } else if (eventName === AppStateEvent.EditorFocused) {
this.toggleDisplayOptionsMenu(false); this.setShowDisplayOptionsMenu(false);
} }
}) })
); );
@@ -169,7 +169,7 @@ export class NotesViewState {
setCompletedFullSync: action, setCompletedFullSync: action,
setNoteFilterText: action, setNoteFilterText: action,
syncSelectedNotes: action, syncSelectedNotes: action,
toggleDisplayOptionsMenu: action, setShowDisplayOptionsMenu: action,
onFilterEnter: action, onFilterEnter: action,
handleFilterTextChanged: action, handleFilterTextChanged: action,
@@ -185,7 +185,7 @@ export class NotesViewState {
this.completedFullSync = completed; this.completedFullSync = completed;
}; };
toggleDisplayOptionsMenu = (enabled: boolean) => { setShowDisplayOptionsMenu = (enabled: boolean) => {
this.showDisplayOptionsMenu = enabled; this.showDisplayOptionsMenu = enabled;
}; };
@@ -505,7 +505,7 @@ export class NotesViewState {
handleTagChange = () => { handleTagChange = () => {
this.resetScrollPosition(); this.resetScrollPosition();
this.toggleDisplayOptionsMenu(false); this.setShowDisplayOptionsMenu(false);
this.setNoteFilterText(''); this.setNoteFilterText('');
this.application.getDesktopService().searchText(); this.application.getDesktopService().searchText();
this.resetPagination(); this.resetPagination();

View File

@@ -683,6 +683,10 @@
top: 50%; top: 50%;
} }
.top-full {
top: 100%;
}
.left-2 { .left-2 {
left: 0.5rem; left: 0.5rem;
} }