fix: make menus scrollable when there's not enough space

This commit is contained in:
Antonella Sgarlatta
2021-05-20 16:57:34 -03:00
parent c7be130a94
commit 6a9d54c2ae
7 changed files with 89 additions and 49 deletions

View File

@@ -31,13 +31,13 @@ const NotesContextMenu = observer(({ appState }: Props) => {
return appState.notes.contextMenuOpen ? (
<div
ref={contextMenuRef}
className="sn-dropdown max-w-80 flex flex-col py-2 absolute"
style={{ ...appState.notes.contextMenuPosition }}
className="sn-dropdown max-h-120 max-w-80 flex flex-col py-2 overflow-y-scroll fixed"
style={{
...appState.notes.contextMenuPosition,
maxHeight: appState.notes.contextMenuMaxHeight,
}}
>
<NotesOptions
appState={appState}
closeOnBlur={closeOnBlur}
/>
<NotesOptions appState={appState} closeOnBlur={closeOnBlur} />
</div>
) : null;
});

View File

@@ -19,11 +19,16 @@ type Props = {
export const NotesOptions = observer(
({ appState, closeOnBlur, onSubmenuChange }: Props) => {
const [tagsMenuOpen, setTagsMenuOpen] = useState(false);
const [tagsMenuPosition, setTagsMenuPosition] = useState({
const [tagsMenuPosition, setTagsMenuPosition] = useState<{
top: number;
right?: number;
left?: number;
}>({
top: 0,
right: 0,
});
const [tagsMenuMaxHeight, setTagsMenuMaxHeight] = useState<number | 'auto'>('auto');
const [tagsMenuMaxHeight, setTagsMenuMaxHeight] =
useState<number | 'auto'>('auto');
const toggleOn = (condition: (note: SNNote) => boolean) => {
const notesMatchingAttribute = notes.filter(condition);
@@ -62,24 +67,27 @@ export const NotesOptions = observer(
const defaultFontSize = window.getComputedStyle(
document.documentElement
).fontSize;
const maxTagsMenuSize = parseFloat(defaultFontSize) * 20;
const { clientWidth, clientHeight } = document.body;
const maxTagsMenuSize = parseFloat(defaultFontSize) * 30;
const { clientWidth, clientHeight } = document.documentElement;
const buttonRect = tagsButtonRef.current.getBoundingClientRect();
const { offsetTop, offsetWidth } = tagsButtonRef.current;
const footerHeight = 32;
if (buttonRect.top + maxTagsMenuSize > clientHeight - footerHeight) {
if ((buttonRect.top + maxTagsMenuSize) > (clientHeight - footerHeight)) {
setTagsMenuMaxHeight(clientHeight - buttonRect.top - footerHeight - 2);
}
setTagsMenuPosition({
top: offsetTop,
right:
buttonRect.right + maxTagsMenuSize >
clientWidth
? offsetWidth
: -offsetWidth,
});
if ((buttonRect.right + maxTagsMenuSize) > clientWidth) {
setTagsMenuPosition({
top: buttonRect.top,
right: clientWidth - buttonRect.left,
});
} else {
setTagsMenuPosition({
top: buttonRect.top,
left: buttonRect.right,
});
}
setTagsMenuOpen(!tagsMenuOpen);
};
@@ -127,10 +135,7 @@ export const NotesOptions = observer(
</Switch>
<div className="h-1px my-2 bg-border"></div>
{appState.tags.tagsCount > 0 && (
<Disclosure
open={tagsMenuOpen}
onChange={openTagsMenu}
>
<Disclosure open={tagsMenuOpen} onChange={openTagsMenu}>
<DisclosureButton
onKeyUp={(event) => {
if (event.key === 'Escape') {
@@ -145,10 +150,7 @@ export const NotesOptions = observer(
<Icon type="hashtag" className={iconClass} />
{'Add tag'}
</div>
<Icon
type="chevron-right"
className="color-neutral"
/>
<Icon type="chevron-right" className="color-neutral" />
</DisclosureButton>
<DisclosurePanel
onKeyUp={(event) => {
@@ -160,8 +162,9 @@ export const NotesOptions = observer(
style={{
...tagsMenuPosition,
maxHeight: tagsMenuMaxHeight,
position: 'fixed',
}}
className="sn-dropdown sn-dropdown-anchor-right flex flex-col py-2 max-h-80 absolute overflow-y-scroll"
className="sn-dropdown flex flex-col py-2 max-h-120 max-w-80 fixed overflow-y-scroll"
>
{appState.tags.tags.map((tag) => (
<button
@@ -278,10 +281,7 @@ export const NotesOptions = observer(
}}
>
<div className="flex items-start">
<Icon
type="trash-sweep"
className="color-danger mr-2"
/>
<Icon type="trash-sweep" className="color-danger mr-2" />
<div className="flex-row">
<div className="color-danger">Empty Trash</div>
<div className="text-xs">

View File

@@ -21,6 +21,7 @@ export const NotesOptionsPanel = observer(({ appState }: Props) => {
top: 0,
right: 0,
});
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto');
const buttonRef = useRef<HTMLButtonElement>();
const panelRef = useRef<HTMLDivElement>();
const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen);
@@ -35,6 +36,9 @@ export const NotesOptionsPanel = observer(({ appState }: Props) => {
open={open}
onChange={() => {
const rect = buttonRef.current.getBoundingClientRect();
const { clientHeight } = document.documentElement;
const footerHeight = 32;
setMaxHeight(clientHeight - rect.bottom - footerHeight - 2);
setPosition({
top: rect.bottom,
right: document.body.clientWidth - rect.right,
@@ -65,8 +69,9 @@ export const NotesOptionsPanel = observer(({ appState }: Props) => {
ref={panelRef}
style={{
...position,
maxHeight
}}
className="sn-dropdown flex flex-col py-2"
className="sn-dropdown max-h-120 max-w-80 flex flex-col py-2 overflow-y-scroll fixed"
>
{open && (
<NotesOptions

View File

@@ -63,7 +63,7 @@ const SearchOptions = observer(({ appState }: Props) => {
style={{
top: optionsPanelTop,
}}
className="sn-dropdown sn-dropdown-anchor-right grid gap-2 py-2"
className="sn-dropdown sn-dropdown-anchor-right absolute grid gap-2 py-2"
>
<Switch
className="h-10"

View File

@@ -27,6 +27,7 @@ export class NotesState {
top: 0,
left: 0,
};
contextMenuMaxHeight: number | 'auto' = 'auto';
showProtectedWarning = false;
constructor(
@@ -45,6 +46,7 @@ export class NotesState {
setContextMenuOpen: action,
setContextMenuPosition: action,
setContextMenuMaxHeight: action,
setShowProtectedWarning: action,
unselectNotes: action,
});
@@ -181,6 +183,10 @@ export class NotesState {
this.contextMenuPosition = position;
}
setContextMenuMaxHeight(maxHeight: number | 'auto'): void {
this.contextMenuMaxHeight = maxHeight;
}
async changeSelectedNotes(
mutate: (mutator: NoteMutator) => void
): Promise<void> {

View File

@@ -308,12 +308,16 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
await this.selectNote(note);
}
if (this.state.selectedNotes[note.uuid]) {
const clientHeight = document.documentElement.clientHeight;
const { clientHeight } = document.documentElement;
const defaultFontSize = window.getComputedStyle(
document.documentElement
).fontSize;
const maxContextMenuHeight = parseFloat(defaultFontSize) * 20;
const maxContextMenuHeight = parseFloat(defaultFontSize) * 30;
const footerHeight = 32;
// Open up-bottom is default behavior
let openUpBottom = true;
const bottomSpace = clientHeight - footerHeight - e.clientY;
const upSpace = e.clientY;
@@ -321,23 +325,41 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
if (maxContextMenuHeight > bottomSpace) {
// If there's enough space, open bottom-up
if (upSpace > maxContextMenuHeight) {
this.appState.notes.setContextMenuPosition({
bottom: clientHeight - e.clientY,
left: e.clientX,
});
// Else, open on top of screen
openUpBottom = false;
this.appState.notes.setContextMenuMaxHeight(
'auto'
);
// Else, reduce max height (menu will be scrollable) and open in whichever direction there's more space
} else {
this.appState.notes.setContextMenuPosition({
top: 2,
left: e.clientX,
});
if (upSpace > bottomSpace) {
this.appState.notes.setContextMenuMaxHeight(
upSpace - 2
);
openUpBottom = false;
} else {
this.appState.notes.setContextMenuMaxHeight(
bottomSpace - 2
);
}
}
} else {
this.appState.notes.setContextMenuMaxHeight(
'auto'
);
}
if (openUpBottom) {
this.appState.notes.setContextMenuPosition({
top: e.clientY,
left: e.clientX,
});
} else {
this.appState.notes.setContextMenuPosition({
bottom: clientHeight - e.clientY,
left: e.clientX,
});
}
this.appState.notes.setContextMenuOpen(true);
}
}

View File

@@ -127,6 +127,10 @@
max-width: 15rem;
}
.max-w-80 {
max-width: 20rem;
}
.h-1px {
height: 1px;
}
@@ -151,8 +155,12 @@
height: 2.5rem;
}
.max-h-80 {
max-height: 20rem;
.max-h-120 {
max-height: 30rem;
}
.fixed {
position: fixed;
}
.overflow-y-scroll {
@@ -198,7 +206,6 @@
}
.sn-dropdown {
@extend .absolute;
@extend .bg-default;
@extend .min-w-80;
@extend .transition-transform;