Skip to content

Commit

Permalink
feat: refactor context menu for button actions for saved searches
Browse files Browse the repository at this point in the history
  • Loading branch information
seanes committed Sep 30, 2024
1 parent f8d47ea commit 93668eb
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 95 deletions.
23 changes: 23 additions & 0 deletions packages/frontend/src/components/Backdrop/useClickOutside.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useRef } from 'react';

export const useClickoutside = (callback: () => void) => {
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
callback();
}
};

document.addEventListener('mouseup', handleClickOutside);
document.addEventListener('touchend', handleClickOutside);

return () => {
document.removeEventListener('mouseup', handleClickOutside);
document.removeEventListener('touchend', handleClickOutside);
};
}, [callback]);

return ref;
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
}

@media screen and (max-width: 1024px) {
.dropdownList {
.mobileDrawer {
position: fixed;
left: 0;
top: 100px;
Expand Down
10 changes: 9 additions & 1 deletion packages/frontend/src/components/DropdownMenu/dropdownList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ interface DropdownListProps {
isExpanded: boolean;
variant?: 'short' | 'medium' | 'long';
className?: string;
disableMobileDrawer?: boolean;
}

export const DropdownList = ({ children, className, variant = 'short', isExpanded }: DropdownListProps) => {
export const DropdownList = ({
children,
className,
variant = 'short',
isExpanded,
disableMobileDrawer = false,
}: DropdownListProps) => {
if (!isExpanded) {
return null;
}
Expand All @@ -19,6 +26,7 @@ export const DropdownList = ({ children, className, variant = 'short', isExpande
[styles.short]: variant === 'short',
[styles.short]: variant === 'medium',
[styles.long]: variant === 'long',
[styles.mobileDrawer]: !disableMobileDrawer,
})}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export const GlobalMenuBar: React.FC<GlobalMenuBarProps> = ({ name, profile, not
const { t } = useTranslation();

const toggleShowBackdrop = () => {
setShowBackDrop((prev) => !prev);
if (!showBackDrop) {
setShowBackDrop((prev) => !prev);
}
};

const handleClose = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export const NavigationDropdownMenu: React.FC<NavigationDropdownMenuProps> = ({
const { t } = useTranslation();

const handleClose = () => {
onClose?.();
setShowSubMenu('none');
onClose?.();
};

if (showSubMenu !== 'none')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ export const EditSavedSearchDialog = forwardRef(
editDialogRef.current?.close();
};

if (typeof savedSearch?.id === 'undefined') return null;

return (
<Modal.Root>
<Modal.Dialog onClose={onClose} ref={editDialogRef} onInteractOutside={onClose}>
Expand Down Expand Up @@ -96,14 +94,15 @@ export const EditSavedSearchDialog = forwardRef(
<div className={styles.searchFormFooter}>
<div className={styles.buttons}>
<Button onClick={handleSave}>{t('editSavedSearch.save_and_close')}</Button>
<Button variant="secondary" onClick={() => onDelete?.(savedSearch)}>
<Button variant="secondary" onClick={() => savedSearch && onDelete?.(savedSearch)}>
<TrashIcon className={styles.icon} aria-hidden="true" />
<span>{t('word.delete')}</span>
</Button>
</div>
<span className={styles.updateTime}>
{t('savedSearches.lastUpdated')}
{autoFormatRelativeTime(new Date(Number.parseInt(savedSearch?.updatedAt, 10)), formatDistance)}
{savedSearch?.updatedAt &&
autoFormatRelativeTime(new Date(Number.parseInt(savedSearch.updatedAt, 10)), formatDistance)}
</span>
</div>
</Modal.Footer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,70 +1,76 @@
import { DropdownMenu } from '@digdir/designsystemet-react';
import { Button } from '@digdir/designsystemet-react';
import { MenuElipsisHorizontalIcon, TrashIcon } from '@navikt/aksel-icons';
import { ChevronRightIcon, PencilIcon } from '@navikt/aksel-icons';
import { PencilIcon } from '@navikt/aksel-icons';
import type { SavedSearchesFieldsFragment } from 'bff-types-generated';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { HorizontalLine } from '../../../components';
import { DropdownList, HorizontalLine } from '../../../components';
import { useClickoutside } from '../../../components/Backdrop/useClickOutside.ts';
import { useEscapeKey } from '../../../components/Backdrop/useEscapeKey.ts';
import { MenuItem } from '../../../components/MenuBar';
import styles from './savedSearchesActions.module.css';

interface SaveSearchesActionsProps {
savedSearch: SavedSearchesFieldsFragment;
onDeleteBtnClick?: (savedSearchToDelete: SavedSearchesFieldsFragment) => void;
onEditBtnClick?: (savedSearch: SavedSearchesFieldsFragment) => void;
}
const SaveSearchesActions = ({ savedSearch, onDeleteBtnClick, onEditBtnClick }: SaveSearchesActionsProps) => {
export const SaveSearchesActions = ({ savedSearch, onDeleteBtnClick, onEditBtnClick }: SaveSearchesActionsProps) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);

if (!savedSearch?.data) return null;
useEscapeKey(() => setIsMenuOpen(false));
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);

const handleOpenEditModal = () => {
onEditBtnClick?.(savedSearch);
setIsMenuOpen(false);
};

const handleDelete = () => {
onDeleteBtnClick?.(savedSearch);
setIsMenuOpen(false);
};

const ref = useClickoutside(() => {
setIsMenuOpen(false);
});

return (
<div className={styles.renderButtons}>
<DropdownMenu.Root open={open} onClose={() => setOpen(false)}>
<DropdownMenu.Trigger className={styles.linkButton} onClick={() => setOpen(!open)}>
<MenuElipsisHorizontalIcon className={styles.icon} />
</DropdownMenu.Trigger>
<DropdownMenu.Content className={styles.dropdownContent}>
<DropdownMenu.Group>
<DropdownMenu.Item onClick={handleOpenEditModal}>
<div className={styles.dropdownEditSearch}>
<PencilIcon fontSize="1.5rem" aria-hidden="true" />
<div className={styles.savedSearchesActions} ref={ref}>
<Button
type="button"
variant="tertiary"
onClick={() => setIsMenuOpen(!isMenuOpen)}
className={styles.triggerButton}
>
<MenuElipsisHorizontalIcon className={styles.icon} />
</Button>
<DropdownList variant="medium" isExpanded={isMenuOpen} disableMobileDrawer className={styles.contextMenu}>
<MenuItem
onClick={handleOpenEditModal}
leftContent={
<MenuItem.LeftContent>
<div className={styles.leftInnerContent}>
<PencilIcon className={styles.icon} />
<span>{t('savedSearches.change_name')}</span>
<ChevronRightIcon fontSize={24} className={styles.icon} />
</div>
</DropdownMenu.Item>
<HorizontalLine />
<DropdownMenu.Item onClick={() => onDeleteBtnClick?.(savedSearch)}>
<div className={styles.dropdownEditSearch}>
<TrashIcon className={styles.icon} aria-hidden="true" />
</MenuItem.LeftContent>
}
count={0}
/>
<HorizontalLine />
<MenuItem
onClick={handleDelete}
leftContent={
<MenuItem.LeftContent>
<div className={styles.leftInnerContent}>
<TrashIcon className={styles.icon} />
<span>{t('savedSearches.delete_search')}</span>
</div>
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</MenuItem.LeftContent>
}
count={0}
/>
</DropdownList>
</div>
);
};

export interface OpenSavedSearchLinkProps {
savedSearch: SavedSearchesFieldsFragment;
onClick?: () => void;
}
const OpenSavedSearchLink = ({ savedSearch, onClick }: OpenSavedSearchLinkProps) => {
const { searchString, filters, fromView } = savedSearch.data;
const queryParams = new URLSearchParams({
...(searchString && { search: searchString }),
...(filters?.length && { filters: JSON.stringify(filters) }),
});
return (
<a href={`${fromView}?${queryParams.toString()}`}>
<ChevronRightIcon fontSize={24} className={styles.icon} onClick={onClick} />
</a>
);
};

export { OpenSavedSearchLink, SaveSearchesActions };
Original file line number Diff line number Diff line change
@@ -1,50 +1,21 @@
.renderButtons {
.savedSearchesActions {
display: flex;
align-items: center;
}

.linkButton {
border: none;
background-color: #fff;
color: var(--black-50, rgba(0, 0, 0, 0.5));
cursor: pointer;
}

.linkButton:hover {
background-color: #fff;
color: var(--black-50, rgba(0, 0, 0, 0.5));
}

.icon {
width: 24px;
height: 24px;
min-width: 24px;
min-height: 24px;
max-width: 24px;
max-height: 24px;
color: #000;
}

.dropdownContent {
border-radius: 0;

li {
margin: 0;
}

button {
border-radius: 2px;
padding: 0;
height: auto;
}
.leftInnerContent {
display: flex;
align-items: center;
column-gap: 0.5rem;
}

.dropdownEditSearch {
width: 100%;
display: grid;
grid-template-columns: min-content auto min-content;
align-items: start;
text-align: left;
gap: 6px;
padding: 6px;

& > svg,
& > span {
padding: 4px;
}
.contextMenu {
right: 1rem;
}

0 comments on commit 93668eb

Please sign in to comment.