feat: move search bar to navigation panel (#1170)
This commit is contained in:
@@ -186,7 +186,6 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
noAccountWarningController={viewControllerManager.noAccountWarningController}
|
noAccountWarningController={viewControllerManager.noAccountWarningController}
|
||||||
noteTagsController={viewControllerManager.noteTagsController}
|
noteTagsController={viewControllerManager.noteTagsController}
|
||||||
notesController={viewControllerManager.notesController}
|
notesController={viewControllerManager.notesController}
|
||||||
searchOptionsController={viewControllerManager.searchOptionsController}
|
|
||||||
selectionController={viewControllerManager.selectionController}
|
selectionController={viewControllerManager.selectionController}
|
||||||
/>
|
/>
|
||||||
<NoteGroupView application={application} />
|
<NoteGroupView application={application} />
|
||||||
|
|||||||
@@ -7,17 +7,13 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = {
|
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',
|
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 }) => (
|
const Bubble: FunctionComponent<Props> = ({ label, selected, onSelect }) => (
|
||||||
<span
|
<span role="tab" className={`${styles.base} ${selected ? styles.selected : styles.unselected}`} onClick={onSelect}>
|
||||||
role="tab"
|
|
||||||
className={`bubble ${styles.base} ${selected ? styles.selected : styles.unselected}`}
|
|
||||||
onClick={onSelect}
|
|
||||||
>
|
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const ContentList: FunctionComponent<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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}
|
id={ElementIds.ContentList}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
|
|||||||
@@ -3,26 +3,15 @@ import { WebApplication } from '@/Application/Application'
|
|||||||
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
||||||
import { PrefKey, SystemViewId } from '@standardnotes/snjs'
|
import { PrefKey, SystemViewId } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import {
|
import { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
ChangeEventHandler,
|
|
||||||
FunctionComponent,
|
|
||||||
KeyboardEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import ContentList from '@/Components/ContentListView/ContentList'
|
import ContentList from '@/Components/ContentListView/ContentList'
|
||||||
import NoAccountWarning from '@/Components/NoAccountWarning/NoAccountWarning'
|
import NoAccountWarning from '@/Components/NoAccountWarning/NoAccountWarning'
|
||||||
import SearchOptions from '@/Components/SearchOptions/SearchOptions'
|
|
||||||
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
||||||
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
|
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
|
||||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { FilesController } from '@/Controllers/FilesController'
|
import { FilesController } from '@/Controllers/FilesController'
|
||||||
import { NoteTagsController } from '@/Controllers/NoteTagsController'
|
import { NoteTagsController } from '@/Controllers/NoteTagsController'
|
||||||
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
|
|
||||||
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
|
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController'
|
||||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
@@ -38,7 +27,6 @@ type Props = {
|
|||||||
noAccountWarningController: NoAccountWarningController
|
noAccountWarningController: NoAccountWarningController
|
||||||
noteTagsController: NoteTagsController
|
noteTagsController: NoteTagsController
|
||||||
notesController: NotesController
|
notesController: NotesController
|
||||||
searchOptionsController: SearchOptionsController
|
|
||||||
selectionController: SelectedItemsController
|
selectionController: SelectedItemsController
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,17 +39,13 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
noAccountWarningController,
|
noAccountWarningController,
|
||||||
noteTagsController,
|
noteTagsController,
|
||||||
notesController,
|
notesController,
|
||||||
searchOptionsController,
|
|
||||||
selectionController,
|
selectionController,
|
||||||
}) => {
|
}) => {
|
||||||
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
|
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
clearFilterText,
|
|
||||||
completedFullSync,
|
completedFullSync,
|
||||||
createNewNote,
|
createNewNote,
|
||||||
noteFilterText,
|
|
||||||
onFilterEnter,
|
|
||||||
optionsSubtitle,
|
optionsSubtitle,
|
||||||
paginate,
|
paginate,
|
||||||
panelTitle,
|
panelTitle,
|
||||||
@@ -70,13 +54,10 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
searchBarElement,
|
searchBarElement,
|
||||||
selectNextItem,
|
selectNextItem,
|
||||||
selectPreviousItem,
|
selectPreviousItem,
|
||||||
setNoteFilterText,
|
|
||||||
} = itemListController
|
} = itemListController
|
||||||
|
|
||||||
const { selectedItems } = selectionController
|
const { selectedItems } = selectionController
|
||||||
|
|
||||||
const [focusedSearch, setFocusedSearch] = useState(false)
|
|
||||||
|
|
||||||
const isFilesSmartView = useMemo(
|
const isFilesSmartView = useMemo(
|
||||||
() => navigationController.selected?.uuid === SystemViewId.Files,
|
() => navigationController.selected?.uuid === SystemViewId.Files,
|
||||||
[navigationController.selected?.uuid],
|
[navigationController.selected?.uuid],
|
||||||
@@ -166,25 +147,6 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
selectionController,
|
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(
|
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
|
||||||
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
||||||
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
|
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
|
||||||
@@ -211,7 +173,7 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
ref={itemsViewPanelRef}
|
ref={itemsViewPanelRef}
|
||||||
>
|
>
|
||||||
<div className="content">
|
<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">
|
<div id="items-title-bar-container">
|
||||||
<ContentListHeader
|
<ContentListHeader
|
||||||
application={application}
|
application={application}
|
||||||
@@ -221,34 +183,6 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
isFilesSmartView={isFilesSmartView}
|
isFilesSmartView={isFilesSmartView}
|
||||||
optionsSubtitle={optionsSubtitle}
|
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
|
<NoAccountWarning
|
||||||
accountMenuController={accountMenuController}
|
accountMenuController={accountMenuController}
|
||||||
noAccountWarningController={noAccountWarningController}
|
noAccountWarningController={noAccountWarningController}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import {
|
|||||||
PremiumFeatureIcon,
|
PremiumFeatureIcon,
|
||||||
RestoreIcon,
|
RestoreIcon,
|
||||||
RichTextIcon,
|
RichTextIcon,
|
||||||
|
SearchIcon,
|
||||||
SecurityIcon,
|
SecurityIcon,
|
||||||
ServerIcon,
|
ServerIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
@@ -75,6 +76,7 @@ import {
|
|||||||
SortDescendingIcon,
|
SortDescendingIcon,
|
||||||
SpreadsheetsIcon,
|
SpreadsheetsIcon,
|
||||||
StarIcon,
|
StarIcon,
|
||||||
|
SubtractIcon,
|
||||||
SyncIcon,
|
SyncIcon,
|
||||||
TasksIcon,
|
TasksIcon,
|
||||||
ThemesIcon,
|
ThemesIcon,
|
||||||
@@ -89,7 +91,6 @@ import {
|
|||||||
UserSwitch,
|
UserSwitch,
|
||||||
WarningIcon,
|
WarningIcon,
|
||||||
WindowIcon,
|
WindowIcon,
|
||||||
SubtractIcon,
|
|
||||||
} from '@standardnotes/icons'
|
} from '@standardnotes/icons'
|
||||||
|
|
||||||
export const ICONS = {
|
export const ICONS = {
|
||||||
@@ -122,13 +123,13 @@ export const ICONS = {
|
|||||||
'menu-arrow-down': MenuArrowDownIcon,
|
'menu-arrow-down': MenuArrowDownIcon,
|
||||||
'menu-arrow-right': MenuArrowRightIcon,
|
'menu-arrow-right': MenuArrowRightIcon,
|
||||||
'menu-close': MenuCloseIcon,
|
'menu-close': MenuCloseIcon,
|
||||||
'sort-descending': SortDescendingIcon,
|
|
||||||
'pencil-filled': PencilFilledIcon,
|
'pencil-filled': PencilFilledIcon,
|
||||||
'pencil-off': PencilOffIcon,
|
'pencil-off': PencilOffIcon,
|
||||||
'pin-filled': PinFilledIcon,
|
'pin-filled': PinFilledIcon,
|
||||||
'plain-text': PlainTextIcon,
|
'plain-text': PlainTextIcon,
|
||||||
'premium-feature': PremiumFeatureIcon,
|
'premium-feature': PremiumFeatureIcon,
|
||||||
'rich-text': RichTextIcon,
|
'rich-text': RichTextIcon,
|
||||||
|
'sort-descending': SortDescendingIcon,
|
||||||
'trash-filled': TrashFilledIcon,
|
'trash-filled': TrashFilledIcon,
|
||||||
'trash-sweep': TrashSweepIcon,
|
'trash-sweep': TrashSweepIcon,
|
||||||
'user-add': UserAddIcon,
|
'user-add': UserAddIcon,
|
||||||
@@ -163,6 +164,7 @@ export const ICONS = {
|
|||||||
pencil: PencilIcon,
|
pencil: PencilIcon,
|
||||||
pin: PinIcon,
|
pin: PinIcon,
|
||||||
restore: RestoreIcon,
|
restore: RestoreIcon,
|
||||||
|
search: SearchIcon,
|
||||||
security: SecurityIcon,
|
security: SecurityIcon,
|
||||||
server: ServerIcon,
|
server: ServerIcon,
|
||||||
settings: SettingsIcon,
|
settings: SettingsIcon,
|
||||||
@@ -170,6 +172,7 @@ export const ICONS = {
|
|||||||
signOut: SignOutIcon,
|
signOut: SignOutIcon,
|
||||||
spreadsheets: SpreadsheetsIcon,
|
spreadsheets: SpreadsheetsIcon,
|
||||||
star: StarIcon,
|
star: StarIcon,
|
||||||
|
subtract: SubtractIcon,
|
||||||
sync: SyncIcon,
|
sync: SyncIcon,
|
||||||
tasks: TasksIcon,
|
tasks: TasksIcon,
|
||||||
themes: ThemesIcon,
|
themes: ThemesIcon,
|
||||||
@@ -180,7 +183,6 @@ export const ICONS = {
|
|||||||
user: UserIcon,
|
user: UserIcon,
|
||||||
warning: WarningIcon,
|
warning: WarningIcon,
|
||||||
window: WindowIcon,
|
window: WindowIcon,
|
||||||
subtract: SubtractIcon,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { forwardRef, Fragment, Ref } from 'react'
|
import { forwardRef, Fragment, Ref } from 'react'
|
||||||
import { DecoratedInputProps } from './DecoratedInputProps'
|
import { DecoratedInputProps } from './DecoratedInputProps'
|
||||||
|
|
||||||
const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean) => {
|
const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean, roundedFull?: boolean) => {
|
||||||
return {
|
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' : ''
|
!hasLeftDecorations && !hasRightDecorations ? 'px-2 py-1.5' : ''
|
||||||
}`,
|
} ${roundedFull ? 'rounded-full' : 'rounded'}`,
|
||||||
input: `w-full border-0 focus:shadow-none focus:outline-none focus:ring-none bg-transparent text-text ${
|
input: `focus:ring-none w-full border-0 bg-transparent text-text focus:shadow-none focus:outline-none ${
|
||||||
!hasLeftDecorations && hasRightDecorations ? 'pl-2' : ''
|
!hasLeftDecorations && hasRightDecorations ? 'pl-2' : ''
|
||||||
} ${hasRightDecorations ? 'pr-2' : ''}`,
|
} ${hasRightDecorations ? 'pr-2' : ''}`,
|
||||||
disabled: 'bg-passive-5 cursor-not-allowed',
|
disabled: 'bg-passive-5 cursor-not-allowed',
|
||||||
@@ -19,27 +19,32 @@ const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean
|
|||||||
const DecoratedInput = forwardRef(
|
const DecoratedInput = forwardRef(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
type = 'text',
|
autocomplete = false,
|
||||||
className = '',
|
className = '',
|
||||||
id = '',
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
id,
|
||||||
left,
|
left,
|
||||||
right,
|
onBlur,
|
||||||
value,
|
|
||||||
placeholder = '',
|
|
||||||
onChange,
|
onChange,
|
||||||
onFocus,
|
onFocus,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
autocomplete = false,
|
onKeyUp,
|
||||||
|
placeholder = '',
|
||||||
|
right,
|
||||||
|
type = 'text',
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
roundedFull,
|
||||||
}: DecoratedInputProps,
|
}: DecoratedInputProps,
|
||||||
ref: Ref<HTMLInputElement>,
|
ref: Ref<HTMLInputElement>,
|
||||||
) => {
|
) => {
|
||||||
const hasLeftDecorations = Boolean(left?.length)
|
const hasLeftDecorations = Boolean(left?.length)
|
||||||
const hasRightDecorations = Boolean(right?.length)
|
const hasRightDecorations = Boolean(right?.length)
|
||||||
const classNames = getClassNames(hasLeftDecorations, hasRightDecorations)
|
const classNames = getClassNames(hasLeftDecorations, hasRightDecorations, roundedFull)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${classNames.container} ${disabled ? classNames.disabled : ''} ${className}`}>
|
<div className={`${classNames.container} ${disabled ? classNames.disabled : ''} ${className}`}>
|
||||||
|
<div className=""></div>
|
||||||
{left && (
|
{left && (
|
||||||
<div className="flex items-center px-2 py-1.5">
|
<div className="flex items-center px-2 py-1.5">
|
||||||
{left.map((leftChild, index) => (
|
{left.map((leftChild, index) => (
|
||||||
@@ -49,18 +54,21 @@ const DecoratedInput = forwardRef(
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type={type}
|
autoComplete={autocomplete ? 'on' : 'off'}
|
||||||
id={id}
|
|
||||||
className={`${classNames.input} ${disabled ? classNames.disabled : ''}`}
|
className={`${classNames.input} ${disabled ? classNames.disabled : ''}`}
|
||||||
|
data-lpignore={type !== 'password' ? true : false}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={value}
|
id={id}
|
||||||
placeholder={placeholder}
|
onBlur={onBlur}
|
||||||
onChange={(e) => onChange && onChange((e.target as HTMLInputElement).value)}
|
onChange={(e) => onChange && onChange((e.target as HTMLInputElement).value)}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
data-lpignore={type !== 'password' ? true : false}
|
onKeyUp={onKeyUp}
|
||||||
autoComplete={autocomplete ? 'on' : 'off'}
|
placeholder={placeholder}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
title={title}
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{right && (
|
{right && (
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { FocusEventHandler, KeyboardEventHandler, ReactNode } from 'react'
|
import { FocusEventHandler, KeyboardEventHandler, ReactNode } from 'react'
|
||||||
|
|
||||||
export type DecoratedInputProps = {
|
export type DecoratedInputProps = {
|
||||||
type?: 'text' | 'email' | 'password'
|
autocomplete?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
id?: string
|
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
id?: string
|
||||||
left?: ReactNode[]
|
left?: ReactNode[]
|
||||||
right?: ReactNode[]
|
onBlur?: FocusEventHandler
|
||||||
value?: string
|
|
||||||
placeholder?: string
|
|
||||||
onChange?: (text: string) => void
|
onChange?: (text: string) => void
|
||||||
onFocus?: FocusEventHandler
|
onFocus?: FocusEventHandler
|
||||||
onKeyDown?: KeyboardEventHandler
|
onKeyDown?: KeyboardEventHandler
|
||||||
autocomplete?: boolean
|
onKeyUp?: KeyboardEventHandler
|
||||||
|
placeholder?: string
|
||||||
|
right?: ReactNode[]
|
||||||
|
title?: string
|
||||||
|
type?: React.HTMLInputTypeAttribute
|
||||||
|
value?: string
|
||||||
|
roundedFull?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
|
|||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
|
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
||||||
|
import SearchBar from '@/Components/SearchBar/SearchBar'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -50,6 +51,10 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
|
|||||||
ref={setRef}
|
ref={setRef}
|
||||||
>
|
>
|
||||||
<div id="navigation-content" className="content">
|
<div id="navigation-content" className="content">
|
||||||
|
<SearchBar
|
||||||
|
itemListController={viewControllerManager.itemListController}
|
||||||
|
searchOptionsController={viewControllerManager.searchOptionsController}
|
||||||
|
/>
|
||||||
<div className="section-title-bar">
|
<div className="section-title-bar">
|
||||||
<div className="section-title-bar-header">
|
<div className="section-title-bar-header">
|
||||||
<div className="title text-sm">
|
<div className="title text-sm">
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import Bubble from '@/Components/Bubble/Bubble'
|
import Bubble from '@/Components/Bubble/Bubble'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
|
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
|
||||||
searchOptions: SearchOptionsController
|
searchOptions: SearchOptionsController
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +15,7 @@ const SearchOptions = ({ searchOptions }: Props) => {
|
|||||||
}, [searchOptions])
|
}, [searchOptions])
|
||||||
|
|
||||||
return (
|
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
|
<Bubble
|
||||||
label="Protected Contents"
|
label="Protected Contents"
|
||||||
selected={includeProtectedContents}
|
selected={includeProtectedContents}
|
||||||
|
|||||||
Reference in New Issue
Block a user