feat: move display options to icon button in header (#1138)

This commit is contained in:
Aman Harwara
2022-06-22 22:30:22 +05:30
committed by GitHub
parent d899e737bc
commit b0911b1574
12 changed files with 214 additions and 120 deletions

View File

@@ -66,7 +66,7 @@ const ContentList: FunctionComponent<Props> = ({
return (
<div
className="infinite-scroll focus:shadow-none focus:outline-none"
className="infinite-scroll border-solid border-0 border-t-1px border-main focus:shadow-none focus:outline-none"
id={ElementIds.ContentList}
onScroll={onScroll}
onKeyDown={onKeyDown}

View File

@@ -17,10 +17,6 @@ import ContentList from '@/Components/ContentListView/ContentList'
import NoAccountWarning from '@/Components/NoAccountWarning/NoAccountWarning'
import SearchOptions from '@/Components/SearchOptions/SearchOptions'
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import ContentListOptionsMenu from './ContentListOptionsMenu'
import Icon from '@/Components/Icon/Icon'
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
@@ -31,6 +27,7 @@ import { NoAccountWarningController } from '@/Controllers/NoAccountWarningContro
import { NotesController } from '@/Controllers/NotesController'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { ElementIds } from '@/Constants/ElementIDs'
import ContentListHeader from './Header/ContentListHeader'
type Props = {
accountMenuController: AccountMenuController
@@ -58,32 +55,28 @@ const ContentListView: FunctionComponent<Props> = ({
selectionController,
}) => {
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
const displayOptionsMenuRef = useRef<HTMLDivElement>(null)
const {
clearFilterText,
completedFullSync,
createNewNote,
noteFilterText,
onFilterEnter,
optionsSubtitle,
paginate,
panelTitle,
panelWidth,
renderedItems,
setNoteFilterText,
searchBarElement,
selectNextItem,
selectPreviousItem,
onFilterEnter,
clearFilterText,
paginate,
panelWidth,
createNewNote,
setNoteFilterText,
} = itemListController
const { selectedItems } = selectionController
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
const [focusedSearch, setFocusedSearch] = useState(false)
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(displayOptionsMenuRef, setShowDisplayOptionsMenu)
const isFilesSmartView = useMemo(
() => navigationController.selected?.uuid === SystemViewId.Files,
[navigationController.selected?.uuid],
@@ -205,10 +198,6 @@ const ContentListView: FunctionComponent<Props> = ({
noteTagsController.reloadTagsContainerMaxWidth()
}, [noteTagsController])
const toggleDisplayOptionsMenu = useCallback(() => {
setShowDisplayOptionsMenu(!showDisplayOptionsMenu)
}, [showDisplayOptionsMenu])
const addButtonLabel = useMemo(
() => (isFilesSmartView ? 'Upload file' : 'Create a new note in the selected tag'),
[isFilesSmartView],
@@ -224,17 +213,14 @@ const ContentListView: FunctionComponent<Props> = ({
<div className="content">
<div id="items-title-bar" className="section-title-bar">
<div id="items-title-bar-container">
<div className="section-title-bar-header">
<div className="sk-h2 font-semibold title">{panelTitle}</div>
<button
className="flex items-center px-5 py-1 bg-contrast hover:brightness-130 color-text border-0 cursor-pointer"
title={addButtonLabel}
aria-label={addButtonLabel}
onClick={addNewItem}
>
<Icon type="add" className="w-3.5 h-3.5" />
</button>
</div>
<ContentListHeader
application={application}
panelTitle={panelTitle}
addButtonLabel={addButtonLabel}
addNewItem={addNewItem}
isFilesSmartView={isFilesSmartView}
optionsSubtitle={optionsSubtitle}
/>
<div className="filter-section" role="search">
<div>
<input
@@ -268,38 +254,6 @@ const ContentListView: FunctionComponent<Props> = ({
noAccountWarningController={noAccountWarningController}
/>
</div>
<div id="items-menu-bar" className="sn-component" ref={displayOptionsMenuRef}>
<div className="sk-app-bar no-edges">
<div className="left">
<Disclosure open={showDisplayOptionsMenu} onChange={toggleDisplayOptionsMenu}>
<DisclosureButton
className={`sk-app-bar-item bg-contrast color-text border-0 focus:shadow-none ${
showDisplayOptionsMenu ? 'selected' : ''
}`}
onBlur={closeDisplayOptMenuOnBlur}
>
<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 && (
<ContentListOptionsMenu
application={application}
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
closeOnBlur={closeDisplayOptMenuOnBlur}
isOpen={showDisplayOptionsMenu}
navigationController={navigationController}
/>
)}
</DisclosurePanel>
</Disclosure>
</div>
</div>
</div>
</div>
{completedFullSync && !renderedItems.length ? <p className="empty-items-list faded">No items.</p> : null}
{!completedFullSync && !renderedItems.length ? <p className="empty-items-list faded">Loading...</p> : null}

View File

@@ -0,0 +1,87 @@
import { WebApplication } from '@/Application/Application'
import { Disclosure, DisclosurePanel } from '@reach/disclosure'
import { memo, useCallback, useRef, useState } from 'react'
import Icon from '../../Icon/Icon'
import { DisplayOptionsMenuPositionProps } from './DisplayOptionsMenuProps'
import DisplayOptionsMenuPortal from './DisplayOptionsMenuPortal'
import StyledDisplayOptionsButton from './StyledDisplayOptionsButton'
type Props = {
application: {
getPreference: WebApplication['getPreference']
setPreference: WebApplication['setPreference']
}
panelTitle: string
addButtonLabel: string
addNewItem: () => void
isFilesSmartView: boolean
optionsSubtitle?: string
}
const ContentListHeader = ({
application,
panelTitle,
addButtonLabel,
addNewItem,
isFilesSmartView,
optionsSubtitle,
}: Props) => {
const [displayOptionsMenuPosition, setDisplayOptionsMenuPosition] = useState<DisplayOptionsMenuPositionProps>()
const displayOptionsContainerRef = useRef<HTMLDivElement>(null)
const displayOptionsButtonRef = useRef<HTMLButtonElement>(null)
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
const toggleDisplayOptionsMenu = useCallback(() => {
if (displayOptionsButtonRef.current) {
const buttonBoundingRect = displayOptionsButtonRef.current.getBoundingClientRect()
setDisplayOptionsMenuPosition({
top: buttonBoundingRect.bottom,
left: buttonBoundingRect.right - buttonBoundingRect.width,
})
}
setShowDisplayOptionsMenu((show) => !show)
}, [])
return (
<div className="section-title-bar-header">
<div className="flex flex-col">
<div className="text-lg font-semibold title">{panelTitle}</div>
{optionsSubtitle && <div className="text-xs color-passive-0">{optionsSubtitle}</div>}
</div>
<div className="flex">
<div className="relative" ref={displayOptionsContainerRef}>
<Disclosure open={showDisplayOptionsMenu} onChange={toggleDisplayOptionsMenu}>
<StyledDisplayOptionsButton pressed={showDisplayOptionsMenu} ref={displayOptionsButtonRef}>
<Icon type="sort-descending" className="w-5 h-5" />
</StyledDisplayOptionsButton>
<DisclosurePanel>
{showDisplayOptionsMenu && displayOptionsMenuPosition && (
<DisplayOptionsMenuPortal
application={application}
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
containerRef={displayOptionsContainerRef}
isOpen={showDisplayOptionsMenu}
isFilesSmartView={isFilesSmartView}
top={displayOptionsMenuPosition.top}
left={displayOptionsMenuPosition.left}
/>
)}
</DisclosurePanel>
</Disclosure>
</div>
<button
className="flex justify-center items-center min-w-8 h-8 ml-3 bg-info hover:brightness-130 color-info-contrast border-1 border-solid border-transparent rounded-full cursor-pointer"
title={addButtonLabel}
aria-label={addButtonLabel}
onClick={addNewItem}
>
<Icon type="add" className="w-5 h-5" />
</button>
</div>
</div>
)
}
export default memo(ContentListHeader)

View File

@@ -1,5 +1,4 @@
import { WebApplication } from '@/Application/Application'
import { CollectionSort, CollectionSortProperty, PrefKey, SystemViewId } from '@standardnotes/snjs'
import { CollectionSort, CollectionSortProperty, PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useState } from 'react'
import Icon from '@/Components/Icon/Icon'
@@ -7,22 +6,13 @@ import Menu from '@/Components/Menu/Menu'
import MenuItem from '@/Components/Menu/MenuItem'
import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator'
import { MenuItemType } from '@/Components/Menu/MenuItemType'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { DisplayOptionsMenuProps } from './DisplayOptionsMenuProps'
type Props = {
application: WebApplication
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
closeDisplayOptionsMenu: () => void
isOpen: boolean
navigationController: NavigationController
}
const ContentListOptionsMenu: FunctionComponent<Props> = ({
const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
closeDisplayOptionsMenu,
closeOnBlur,
application,
isOpen,
navigationController,
isFilesSmartView,
}) => {
const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt))
const [sortReverse, setSortReverse] = useState(() => application.getPreference(PrefKey.SortNotesReverse, false))
@@ -109,9 +99,9 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
return (
<Menu
className={
'sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \
'py-1 sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \
border-1 border-solid border-main text-sm z-index-dropdown-menu \
flex flex-col py-2 top-full left-2 absolute'
flex flex-col'
}
a11yLabel="Notes list options menu"
closeMenu={closeDisplayOptionsMenu}
@@ -123,7 +113,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
type={MenuItemType.RadioButton}
onClick={toggleSortByDateModified}
checked={sortBy === CollectionSort.UpdatedAt}
onBlur={closeOnBlur}
>
<div className="flex flex-grow items-center justify-between ml-2">
<span>Date modified</span>
@@ -141,7 +130,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
type={MenuItemType.RadioButton}
onClick={toggleSortByCreationDate}
checked={sortBy === CollectionSort.CreatedAt}
onBlur={closeOnBlur}
>
<div className="flex flex-grow items-center justify-between ml-2">
<span>Creation date</span>
@@ -159,7 +147,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
type={MenuItemType.RadioButton}
onClick={toggleSortByTitle}
checked={sortBy === CollectionSort.Title}
onBlur={closeOnBlur}
>
<div className="flex flex-grow items-center justify-between ml-2">
<span>Title</span>
@@ -174,13 +161,12 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
</MenuItem>
<MenuItemSeparator />
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">View</div>
{navigationController.selectedUuid !== SystemViewId.Files && (
{!isFilesSmartView && (
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hidePreview}
onChange={toggleHidePreview}
onBlur={closeOnBlur}
>
<div className="flex flex-col max-w-3/4">Show note preview</div>
</MenuItem>
@@ -190,7 +176,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideDate}
onChange={toggleHideDate}
onBlur={closeOnBlur}
>
Show date
</MenuItem>
@@ -199,7 +184,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideTags}
onChange={toggleHideTags}
onBlur={closeOnBlur}
>
Show tags
</MenuItem>
@@ -208,7 +192,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideEditorIcon}
onChange={toggleEditorIcon}
onBlur={closeOnBlur}
>
Show icon
</MenuItem>
@@ -219,7 +202,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hidePinned}
onChange={toggleHidePinned}
onBlur={closeOnBlur}
>
Show pinned
</MenuItem>
@@ -228,7 +210,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideProtected}
onChange={toggleHideProtected}
onBlur={closeOnBlur}
>
Show protected
</MenuItem>
@@ -237,7 +218,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={showArchived}
onChange={toggleShowArchived}
onBlur={closeOnBlur}
>
Show archived
</MenuItem>
@@ -246,7 +226,6 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={showTrashed}
onChange={toggleShowTrashed}
onBlur={closeOnBlur}
>
Show trashed
</MenuItem>
@@ -254,4 +233,4 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
)
}
export default observer(ContentListOptionsMenu)
export default observer(DisplayOptionsMenu)

View File

@@ -0,0 +1,63 @@
import { createPortal } from 'react-dom'
import styled from 'styled-components'
import { DisplayOptionsMenuPositionProps, DisplayOptionsMenuProps } from './DisplayOptionsMenuProps'
import DisplayOptionsMenu from './DisplayOptionsMenu'
import { useRef, useEffect, RefObject } from 'react'
type Props = DisplayOptionsMenuProps &
DisplayOptionsMenuPositionProps & {
containerRef: RefObject<HTMLDivElement>
}
const PositionedOptionsMenu = styled.div<DisplayOptionsMenuPositionProps>`
position: absolute;
top: ${(props) => props.top}px;
left: ${(props) => props.left}px;
z-index: var(--z-index-dropdown-menu);
`
const DisplayOptionsMenuPortal = ({
application,
closeDisplayOptionsMenu,
containerRef,
isFilesSmartView,
isOpen,
top,
left,
}: Props) => {
const menuRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const closeIfClickedOutside = (event: MouseEvent) => {
const isDescendantOfMenu = menuRef.current?.contains(event.target as Node)
const isDescendantOfContainer = containerRef.current?.contains(event.target as Node)
if (!isDescendantOfMenu && !isDescendantOfContainer) {
closeDisplayOptionsMenu()
}
}
document.addEventListener('click', closeIfClickedOutside, { capture: true })
return () => {
document.removeEventListener('click', closeIfClickedOutside, {
capture: true,
})
}
}, [closeDisplayOptionsMenu, containerRef])
return createPortal(
<PositionedOptionsMenu top={top} left={left} ref={menuRef}>
<div className="sn-component">
<DisplayOptionsMenu
application={application}
closeDisplayOptionsMenu={closeDisplayOptionsMenu}
isFilesSmartView={isFilesSmartView}
isOpen={isOpen}
/>
</div>
</PositionedOptionsMenu>,
document.body,
)
}
export default DisplayOptionsMenuPortal

View File

@@ -0,0 +1,16 @@
import { WebApplication } from '@/Application/Application'
export type DisplayOptionsMenuPositionProps = {
top: number
left: number
}
export type DisplayOptionsMenuProps = {
application: {
getPreference: WebApplication['getPreference']
setPreference: WebApplication['setPreference']
}
closeDisplayOptionsMenu: () => void
isOpen: boolean
isFilesSmartView: boolean
}

View File

@@ -0,0 +1,13 @@
import { DisclosureButton } from '@reach/disclosure'
import styled from 'styled-components'
const StyledDisplayOptionsButton = styled(DisclosureButton).attrs(() => ({
className:
'flex justify-center items-center min-w-8 h-8 bg-color-padding hover:bg-contrast focus:bg-contrast color-neutral border-1 border-solid border-main rounded-full cursor-pointer',
}))<{
pressed: boolean
}>`
background-color: ${(props) => (props.pressed ? 'var(--sn-stylekit-contrast-background-color)' : 'transparent')};
`
export default StyledDisplayOptionsButton

View File

@@ -72,6 +72,7 @@ import {
SettingsIcon,
SignInIcon,
SignOutIcon,
SortDescendingIcon,
SpreadsheetsIcon,
StarIcon,
SyncIcon,
@@ -121,6 +122,7 @@ export const ICONS = {
'menu-arrow-down': MenuArrowDownIcon,
'menu-arrow-right': MenuArrowRightIcon,
'menu-close': MenuCloseIcon,
'sort-descending': SortDescendingIcon,
'pencil-filled': PencilFilledIcon,
'pencil-off': PencilOffIcon,
'pin-filled': PinFilledIcon,

View File

@@ -503,38 +503,18 @@ export class ItemListController extends AbstractViewController implements Intern
return this.createNewNote()
}
get optionsSubtitle(): string {
let base = ''
if (this.displayOptions.sortBy === CollectionSort.CreatedAt) {
base += ' Date Added'
} else if (this.displayOptions.sortBy === CollectionSort.UpdatedAt) {
base += ' Date Modified'
} else if (this.displayOptions.sortBy === CollectionSort.Title) {
base += ' Title'
get optionsSubtitle(): string | undefined {
if (!this.displayOptions.includePinned && !this.displayOptions.includeProtected) {
return 'Excluding pinned and protected'
}
if (this.displayOptions.includeArchived) {
base += ' | + Archived'
}
if (this.displayOptions.includeTrashed) {
base += ' | + Trashed'
}
if (!this.displayOptions.includePinned) {
base += ' | Pinned'
return 'Excluding pinned'
}
if (!this.displayOptions.includeProtected) {
base += ' | Protected'
return 'Excluding protected'
}
if (this.displayOptions.sortDirection === 'asc') {
base += ' | Reversed'
}
return base
return undefined
}
paginate = () => {

View File

@@ -1,7 +1,6 @@
$z-index-editor-content: 10;
$z-index-editor-title-bar: 100;
$z-index-dropdown-menu: 1002;
$z-index-resizer-overlay: 1000;
@@ -20,6 +19,7 @@ $z-index-modal: 10000;
:root {
--z-index-panel-resizer: 1001;
--z-index-dropdown-menu: 1002;
}
html {

View File

@@ -10,7 +10,7 @@
left: 0;
float: left;
min-width: 160px;
z-index: $z-index-dropdown-menu;
z-index: var(--z-index-dropdown-menu);
margin-top: 5px;
width: 280px;

View File

@@ -17,7 +17,7 @@
@extend .rounded;
@extend .box-shadow;
z-index: $z-index-dropdown-menu;
z-index: var(--z-index-dropdown-menu);
&.sn-dropdown--anchor-right {
right: 0;