diff --git a/app/assets/javascripts/components/Bubble.tsx b/app/assets/javascripts/components/Bubble.tsx new file mode 100644 index 000000000..e164a4f50 --- /dev/null +++ b/app/assets/javascripts/components/Bubble.tsx @@ -0,0 +1,25 @@ +interface BubbleProperties { + label: string; + selected: boolean; + onSelect: () => void; +} + +const styles = { + base: 'px-2 py-1.5 text-center rounded-full cursor-pointer transition border-1 border-solid active:border-info active:bg-info active:color-neutral-contrast', + unselected: 'color-neutral border-secondary', + selected: 'border-info bg-info color-neutral-contrast', +}; + +const Bubble = ({ label, selected, onSelect }: BubbleProperties) => ( + + {label} + +); + +export default Bubble; diff --git a/app/assets/javascripts/components/NotesView.tsx b/app/assets/javascripts/components/NotesView.tsx index 41001045f..6c6a19f23 100644 --- a/app/assets/javascripts/components/NotesView.tsx +++ b/app/assets/javascripts/components/NotesView.tsx @@ -48,13 +48,13 @@ export const NotesView: FunctionComponent = observer( selectPreviousNote, onFilterEnter, handleFilterTextChanged, - onSearchInputBlur, clearFilterText, paginate, panelWidth, } = appState.notesView; const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false); + const [focusedSearch, setFocusedSearch] = useState(false); const [closeDisplayOptMenuOnBlur] = useCloseOnBlur( displayOptionsMenuRef, @@ -130,6 +130,9 @@ export const NotesView: FunctionComponent = observer( setNoteFilterText((e.target as HTMLInputElement).value); }; + const onSearchFocused = () => setFocusedSearch(true); + const onSearchBlurred = () => setFocusedSearch(false); + const onNoteFilterKeyUp = (e: KeyboardEvent) => { if (e.key === KeyboardKey.Enter) { onFilterEnter(); @@ -179,32 +182,38 @@ export const NotesView: FunctionComponent = observer(
- onSearchInputBlur()} - /> - {noteFilterText ? ( - - ) : null} -
- + + {noteFilterText && ( + + )}
+ + {(focusedSearch || noteFilterText) && ( +
+ +
+ )}
diff --git a/app/assets/javascripts/components/SearchOptions.tsx b/app/assets/javascripts/components/SearchOptions.tsx index 0c07dbb55..c6abf6e0e 100644 --- a/app/assets/javascripts/components/SearchOptions.tsx +++ b/app/assets/javascripts/components/SearchOptions.tsx @@ -1,16 +1,7 @@ import { AppState } from '@/ui_models/app_state'; -import { Icon } from './Icon'; -import { useCloseOnBlur } from './utils'; -import { useEffect, useRef, useState } from 'preact/hooks'; import { WebApplication } from '@/ui_models/application'; -import VisuallyHidden from '@reach/visually-hidden'; -import { - Disclosure, - DisclosureButton, - DisclosurePanel, -} from '@reach/disclosure'; -import { Switch } from './Switch'; import { observer } from 'mobx-react-lite'; +import Bubble from './Bubble'; type Props = { appState: AppState; @@ -23,94 +14,33 @@ export const SearchOptions = observer(({ appState }: Props) => { const { includeProtectedContents, includeArchived, includeTrashed } = searchOptions; - const [open, setOpen] = useState(false); - const [position, setPosition] = useState({ - top: 0, - right: 0, - }); - const [maxWidth, setMaxWidth] = useState('auto'); - const buttonRef = useRef(null); - const panelRef = useRef(null); - const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur( - panelRef as any, - setOpen - ); - async function toggleIncludeProtectedContents() { - setLockCloseOnBlur(true); - try { - await searchOptions.toggleIncludeProtectedContents(); - } finally { - setLockCloseOnBlur(false); - } + await searchOptions.toggleIncludeProtectedContents(); } - const updateWidthAndPosition = () => { - const rect = buttonRef.current!.getBoundingClientRect(); - setMaxWidth(rect.right - 16); - setPosition({ - top: rect.bottom, - right: document.body.clientWidth - rect.right, - }); - }; - - useEffect(() => { - window.addEventListener('resize', updateWidthAndPosition); - return () => { - window.removeEventListener('resize', updateWidthAndPosition); - }; - }, []); - return ( - { - updateWidthAndPosition(); - setOpen(!open); - }} +
e.preventDefault()} > - - Search options - - - - -

Include protected contents

-
- -

Include archived notes

-
- -

Include trashed notes

-
-
- + + + + + +
); }); diff --git a/app/assets/javascripts/ui_models/app_state/notes_view_state.ts b/app/assets/javascripts/ui_models/app_state/notes_view_state.ts index c22e3ce1c..9aaba02a7 100644 --- a/app/assets/javascripts/ui_models/app_state/notes_view_state.ts +++ b/app/assets/javascripts/ui_models/app_state/notes_view_state.ts @@ -559,10 +559,6 @@ export class NotesViewState { this.reloadNotes(); }; - onSearchInputBlur = () => { - this.appState.searchOptions.refreshIncludeProtectedContents(); - }; - clearFilterText = () => { this.setNoteFilterText(''); this.onFilterEnter(); diff --git a/app/assets/stylesheets/_notes.scss b/app/assets/stylesheets/_notes.scss index 9308c94bc..3e8cf1cd1 100644 --- a/app/assets/stylesheets/_notes.scss +++ b/app/assets/stylesheets/_notes.scss @@ -52,11 +52,11 @@ .filter-section { clear: left; - height: 28px; - margin-top: 14px; + max-height: 80px; + margin-top: 10px; position: relative; display: flex; - align-items: center; + flex-direction: column; .filter-bar { background-color: var(--sn-stylekit-contrast-background-color); @@ -71,6 +71,20 @@ border-color: transparent; width: 100%; position: relative; + height: 28px; + } + + .search-options { + margin-top: 10px; + + display: grid; + grid-template-columns: repeat( 3, 1fr ); + gap: .5rem; + + font-size: var(--sn-stylekit-font-size-p); + white-space: nowrap; + + overflow-x: auto; } #search-clear-button { @@ -86,9 +100,9 @@ line-height: 17px; text-align: center; position: absolute; - top: 50%; + top: 20%; transform: translateY(-50%); - right: 36px; + right: 10px; cursor: pointer; transition: background-color 0.15s linear; diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 4c2fc3caa..503ca21b4 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -436,6 +436,10 @@ border-color: var(--sn-stylekit-neutral-contrast-color); } +.border-secondary { + border-color: var(--sn-stylekit-secondary-border-color); +} + .sn-component .border-r-1px { border-right-width: 1px; } @@ -938,3 +942,42 @@ .invisible { visibility: hidden; } + +.color-neutral-contrast { + color: var(--sn-stylekit-neutral-contrast-color); +} + +.active\:bg-info:active { + background-color: var(--sn-stylekit-info-color); +} + +.active\:border-info:active { + border-color: var(--sn-stylekit-info-color); +} + +.active\:color-neutral-contrast:active { + color: var(--sn-stylekit-neutral-contrast-color); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-timing-function: cubic-bezier(.4,0,.2,1); + transition-duration: 100ms; +} + +.animate-fade-from-top { + animation: fade-from-top .2s ease-out; +} + +@keyframes fade-from-top { + 0% { + opacity: 0; + transform: translateY(-20%); + } + 75% { + opacity: 1; + } + 100% { + transform: translateY(0%); + } +}