feat: move search bar to navigation panel (#1170)

This commit is contained in:
Aman Harwara
2022-06-28 18:20:31 +05:30
committed by GitHub
parent fb0684a19c
commit 7b51fa4fa8
10 changed files with 134 additions and 106 deletions

View File

@@ -186,7 +186,6 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
noAccountWarningController={viewControllerManager.noAccountWarningController}
noteTagsController={viewControllerManager.noteTagsController}
notesController={viewControllerManager.notesController}
searchOptionsController={viewControllerManager.searchOptionsController}
selectionController={viewControllerManager.selectionController}
/>
<NoteGroupView application={application} />

View File

@@ -7,17 +7,13 @@ type Props = {
}
const styles = {
base: 'px-2 py-1 text-center rounded-full cursor-pointer transition border border-solid active:border-info active:bg-info active:text-neutral-contrast',
base: 'active:border-info active:bg-info active:text-neutral-contrast flex-grow cursor-pointer rounded-full border border-solid px-2 py-1 text-center transition',
unselected: 'text-neutral border-secondary-border',
selected: 'border-info bg-info text-neutral-contrast',
selected: 'text-neutral-contrast border-info bg-info',
}
const Bubble: FunctionComponent<Props> = ({ label, selected, onSelect }) => (
<span
role="tab"
className={`bubble ${styles.base} ${selected ? styles.selected : styles.unselected}`}
onClick={onSelect}
>
<span role="tab" className={`${styles.base} ${selected ? styles.selected : styles.unselected}`} onClick={onSelect}>
{label}
</span>
)

View File

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

View File

@@ -3,26 +3,15 @@ import { WebApplication } from '@/Application/Application'
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
import { PrefKey, SystemViewId } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import {
ChangeEventHandler,
FunctionComponent,
KeyboardEventHandler,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react'
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 { ItemListController } from '@/Controllers/ItemList/ItemListController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { FilesController } from '@/Controllers/FilesController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
import { NotesController } from '@/Controllers/NotesController'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
@@ -38,7 +27,6 @@ type Props = {
noAccountWarningController: NoAccountWarningController
noteTagsController: NoteTagsController
notesController: NotesController
searchOptionsController: SearchOptionsController
selectionController: SelectedItemsController
}
@@ -51,17 +39,13 @@ const ContentListView: FunctionComponent<Props> = ({
noAccountWarningController,
noteTagsController,
notesController,
searchOptionsController,
selectionController,
}) => {
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
const {
clearFilterText,
completedFullSync,
createNewNote,
noteFilterText,
onFilterEnter,
optionsSubtitle,
paginate,
panelTitle,
@@ -70,13 +54,10 @@ const ContentListView: FunctionComponent<Props> = ({
searchBarElement,
selectNextItem,
selectPreviousItem,
setNoteFilterText,
} = itemListController
const { selectedItems } = selectionController
const [focusedSearch, setFocusedSearch] = useState(false)
const isFilesSmartView = useMemo(
() => navigationController.selected?.uuid === SystemViewId.Files,
[navigationController.selected?.uuid],
@@ -166,25 +147,6 @@ const ContentListView: FunctionComponent<Props> = ({
selectionController,
])
const onNoteFilterTextChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
setNoteFilterText(e.target.value)
},
[setNoteFilterText],
)
const onSearchFocused = useCallback(() => setFocusedSearch(true), [])
const onSearchBlurred = useCallback(() => setFocusedSearch(false), [])
const onNoteFilterKeyUp: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
onFilterEnter()
}
},
[onFilterEnter],
)
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
@@ -211,7 +173,7 @@ const ContentListView: FunctionComponent<Props> = ({
ref={itemsViewPanelRef}
>
<div className="content">
<div id="items-title-bar" className="section-title-bar">
<div id="items-title-bar" className="section-title-bar border-b border-solid border-border">
<div id="items-title-bar-container">
<ContentListHeader
application={application}
@@ -221,34 +183,6 @@ const ContentListView: FunctionComponent<Props> = ({
isFilesSmartView={isFilesSmartView}
optionsSubtitle={optionsSubtitle}
/>
<div className="filter-section" role="search">
<div>
<input
type="text"
id="search-bar"
className="filter-bar"
placeholder="Search"
title="Searches notes and files in the currently selected tag"
value={noteFilterText}
onChange={onNoteFilterTextChange}
onKeyUp={onNoteFilterKeyUp}
onFocus={onSearchFocused}
onBlur={onSearchBlurred}
autoComplete="off"
/>
{noteFilterText && (
<button onClick={clearFilterText} id="search-clear-button">
</button>
)}
</div>
{(focusedSearch || noteFilterText) && (
<div className="animate-fade-from-top">
<SearchOptions application={application} searchOptions={searchOptionsController} />
</div>
)}
</div>
<NoAccountWarning
accountMenuController={accountMenuController}
noAccountWarningController={noAccountWarningController}

View File

@@ -67,6 +67,7 @@ import {
PremiumFeatureIcon,
RestoreIcon,
RichTextIcon,
SearchIcon,
SecurityIcon,
ServerIcon,
SettingsIcon,
@@ -75,6 +76,7 @@ import {
SortDescendingIcon,
SpreadsheetsIcon,
StarIcon,
SubtractIcon,
SyncIcon,
TasksIcon,
ThemesIcon,
@@ -89,7 +91,6 @@ import {
UserSwitch,
WarningIcon,
WindowIcon,
SubtractIcon,
} from '@standardnotes/icons'
export const ICONS = {
@@ -122,13 +123,13 @@ 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,
'plain-text': PlainTextIcon,
'premium-feature': PremiumFeatureIcon,
'rich-text': RichTextIcon,
'sort-descending': SortDescendingIcon,
'trash-filled': TrashFilledIcon,
'trash-sweep': TrashSweepIcon,
'user-add': UserAddIcon,
@@ -163,6 +164,7 @@ export const ICONS = {
pencil: PencilIcon,
pin: PinIcon,
restore: RestoreIcon,
search: SearchIcon,
security: SecurityIcon,
server: ServerIcon,
settings: SettingsIcon,
@@ -170,6 +172,7 @@ export const ICONS = {
signOut: SignOutIcon,
spreadsheets: SpreadsheetsIcon,
star: StarIcon,
subtract: SubtractIcon,
sync: SyncIcon,
tasks: TasksIcon,
themes: ThemesIcon,
@@ -180,7 +183,6 @@ export const ICONS = {
user: UserIcon,
warning: WarningIcon,
window: WindowIcon,
subtract: SubtractIcon,
}
type Props = {

View File

@@ -1,12 +1,12 @@
import { forwardRef, Fragment, Ref } from 'react'
import { DecoratedInputProps } from './DecoratedInputProps'
const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean) => {
const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean, roundedFull?: boolean) => {
return {
container: `flex items-stretch position-relative bg-default border border-solid border-border rounded focus-within:ring-2 focus-within:ring-info overflow-hidden text-sm ${
container: `position-relative flex items-stretch overflow-hidden border border-solid border-border bg-default text-sm focus-within:ring-2 focus-within:ring-info ${
!hasLeftDecorations && !hasRightDecorations ? 'px-2 py-1.5' : ''
}`,
input: `w-full border-0 focus:shadow-none focus:outline-none focus:ring-none bg-transparent text-text ${
} ${roundedFull ? 'rounded-full' : 'rounded'}`,
input: `focus:ring-none w-full border-0 bg-transparent text-text focus:shadow-none focus:outline-none ${
!hasLeftDecorations && hasRightDecorations ? 'pl-2' : ''
} ${hasRightDecorations ? 'pr-2' : ''}`,
disabled: 'bg-passive-5 cursor-not-allowed',
@@ -19,27 +19,32 @@ const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean
const DecoratedInput = forwardRef(
(
{
type = 'text',
autocomplete = false,
className = '',
id = '',
disabled = false,
id,
left,
right,
value,
placeholder = '',
onBlur,
onChange,
onFocus,
onKeyDown,
autocomplete = false,
onKeyUp,
placeholder = '',
right,
type = 'text',
title,
value,
roundedFull,
}: DecoratedInputProps,
ref: Ref<HTMLInputElement>,
) => {
const hasLeftDecorations = Boolean(left?.length)
const hasRightDecorations = Boolean(right?.length)
const classNames = getClassNames(hasLeftDecorations, hasRightDecorations)
const classNames = getClassNames(hasLeftDecorations, hasRightDecorations, roundedFull)
return (
<div className={`${classNames.container} ${disabled ? classNames.disabled : ''} ${className}`}>
<div className=""></div>
{left && (
<div className="flex items-center px-2 py-1.5">
{left.map((leftChild, index) => (
@@ -49,18 +54,21 @@ const DecoratedInput = forwardRef(
)}
<input
type={type}
id={id}
autoComplete={autocomplete ? 'on' : 'off'}
className={`${classNames.input} ${disabled ? classNames.disabled : ''}`}
data-lpignore={type !== 'password' ? true : false}
disabled={disabled}
value={value}
placeholder={placeholder}
id={id}
onBlur={onBlur}
onChange={(e) => onChange && onChange((e.target as HTMLInputElement).value)}
onFocus={onFocus}
onKeyDown={onKeyDown}
data-lpignore={type !== 'password' ? true : false}
autoComplete={autocomplete ? 'on' : 'off'}
onKeyUp={onKeyUp}
placeholder={placeholder}
ref={ref}
title={title}
type={type}
value={value}
/>
{right && (

View File

@@ -1,16 +1,20 @@
import { FocusEventHandler, KeyboardEventHandler, ReactNode } from 'react'
export type DecoratedInputProps = {
type?: 'text' | 'email' | 'password'
autocomplete?: boolean
className?: string
id?: string
disabled?: boolean
id?: string
left?: ReactNode[]
right?: ReactNode[]
value?: string
placeholder?: string
onBlur?: FocusEventHandler
onChange?: (text: string) => void
onFocus?: FocusEventHandler
onKeyDown?: KeyboardEventHandler
autocomplete?: boolean
onKeyUp?: KeyboardEventHandler
placeholder?: string
right?: ReactNode[]
title?: string
type?: React.HTMLInputTypeAttribute
value?: string
roundedFull?: boolean
}

View File

@@ -6,6 +6,7 @@ import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import SearchBar from '@/Components/SearchBar/SearchBar'
type Props = {
application: WebApplication
@@ -50,6 +51,10 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
ref={setRef}
>
<div id="navigation-content" className="content">
<SearchBar
itemListController={viewControllerManager.itemListController}
searchOptionsController={viewControllerManager.searchOptionsController}
/>
<div className="section-title-bar">
<div className="section-title-bar-header">
<div className="title text-sm">

View File

@@ -0,0 +1,82 @@
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
import { KeyboardKey } from '@/Services/IOService'
import { useState, useCallback, KeyboardEventHandler, useRef } from 'react'
import SearchOptions from '@/Components/SearchOptions/SearchOptions'
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
import Icon from '../Icon/Icon'
import DecoratedInput from '../Input/DecoratedInput'
import { observer } from 'mobx-react-lite'
type Props = {
itemListController: ItemListController
searchOptionsController: SearchOptionsController
}
const SearchBar = ({ itemListController, searchOptionsController }: Props) => {
const searchInputRef = useRef<HTMLInputElement>(null)
const { noteFilterText, setNoteFilterText, clearFilterText, onFilterEnter } = itemListController
const [focusedSearch, setFocusedSearch] = useState(false)
const onNoteFilterTextChange = useCallback(
(text: string) => {
setNoteFilterText(text)
},
[setNoteFilterText],
)
const onNoteFilterKeyUp: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
onFilterEnter()
}
},
[onFilterEnter],
)
const onSearchFocus = useCallback(() => setFocusedSearch(true), [])
const onSearchBlur = useCallback(() => setFocusedSearch(false), [])
const onClearSearch = useCallback(() => {
clearFilterText()
searchInputRef.current?.focus()
}, [clearFilterText])
return (
<div className="px-2.5 pt-2.5 pb-0.5" role="search">
<DecoratedInput
autocomplete={false}
title="Searches notes and files in the currently selected tag"
className="placeholder:color-passive-0 rounded-full bg-contrast bg-clip-padding px-1"
placeholder="Search"
value={noteFilterText}
ref={searchInputRef}
onBlur={onSearchBlur}
onChange={onNoteFilterTextChange}
onFocus={onSearchFocus}
onKeyUp={onNoteFilterKeyUp}
left={[<Icon type="search" className="mr-1 h-4.5 w-4.5 flex-shrink-0 text-passive-1" />]}
right={[
noteFilterText && (
<button
onClick={onClearSearch}
className="flex h-4.5 w-4.5 items-center justify-center rounded-full border-0 bg-neutral text-neutral-contrast"
>
<Icon type="close" className="h-3.5 w-3.5" />
</button>
),
]}
roundedFull
/>
{(focusedSearch || noteFilterText) && (
<div className="animate-fade-from-top">
<SearchOptions searchOptions={searchOptionsController} />
</div>
)}
</div>
)
}
export default observer(SearchBar)

View File

@@ -1,11 +1,9 @@
import { WebApplication } from '@/Application/Application'
import { observer } from 'mobx-react-lite'
import Bubble from '@/Components/Bubble/Bubble'
import { useCallback } from 'react'
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
type Props = {
application: WebApplication
searchOptions: SearchOptionsController
}
@@ -17,7 +15,7 @@ const SearchOptions = ({ searchOptions }: Props) => {
}, [searchOptions])
return (
<div role="tablist" className="search-options justify-center" onMouseDown={(e) => e.preventDefault()}>
<div role="tablist" className="mt-3 flex flex-wrap gap-2" onMouseDown={(e) => e.preventDefault()}>
<Bubble
label="Protected Contents"
selected={includeProtectedContents}