mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-12 16:35:57 -05:00
Redesign: Show menu as sheet on mobile (#40441)
This commit is contained in:
63
app/javascript/mastodon/components/bottom_sheet/index.tsx
Normal file
63
app/javascript/mastodon/components/bottom_sheet/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { ComponentPropsWithRef } from 'react';
|
||||
import { useLayoutEffect, useRef } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { useMergedRefs } from '@/mastodon/hooks/useMergedRefs';
|
||||
import { useOnClickOutside } from '@/mastodon/hooks/useOnClickOutside';
|
||||
import { useScrollSensor } from '@/mastodon/hooks/useScrollSensor';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
interface BottomSheetProps extends Omit<
|
||||
ComponentPropsWithRef<'dialog'>,
|
||||
'onClose'
|
||||
> {
|
||||
onClose: (e: Event) => void;
|
||||
}
|
||||
|
||||
export const BottomSheet: React.FC<BottomSheetProps> = ({
|
||||
children,
|
||||
className,
|
||||
onClose,
|
||||
...props
|
||||
}) => {
|
||||
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const dialog = dialogRef.current;
|
||||
if (!dialog) return;
|
||||
|
||||
dialog.showModal();
|
||||
|
||||
return () => {
|
||||
dialog.requestClose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useOnClickOutside(contentRef, (e) => {
|
||||
onClose(e);
|
||||
});
|
||||
|
||||
const { sensor, isInViewport } = useScrollSensor({
|
||||
placement: 'bottom',
|
||||
tolerance: 10,
|
||||
});
|
||||
|
||||
return (
|
||||
<dialog
|
||||
{...props}
|
||||
className={classNames(
|
||||
classes.dialog,
|
||||
!isInViewport && classes.withGradient,
|
||||
)}
|
||||
ref={useMergedRefs(props.ref, dialogRef)}
|
||||
>
|
||||
<div ref={contentRef} className={classNames(className, classes.sheet)}>
|
||||
{children}
|
||||
</div>
|
||||
{sensor}
|
||||
</dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
@use '@/styles/mastodon/mixins';
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
max-width: unset;
|
||||
max-height: 100dvh;
|
||||
margin: initial;
|
||||
margin-top: auto;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
overscroll-behavior: contain;
|
||||
|
||||
&::backdrop {
|
||||
opacity: 0.9;
|
||||
background-color: var(--color-bg-overlay);
|
||||
}
|
||||
|
||||
&::after {
|
||||
@include mixins.scroll-gradient(bottom);
|
||||
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
transition: opacity ease 250ms;
|
||||
}
|
||||
}
|
||||
|
||||
.withGradient::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sheet {
|
||||
padding: var(--space-md);
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
background: var(--color-bg-primary);
|
||||
overflow: hidden;
|
||||
text-align: start;
|
||||
|
||||
@include mixins.elevation-1;
|
||||
|
||||
border-inline: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { useBreakpoint } from '@/mastodon/features/ui/hooks/useBreakpoint';
|
||||
import type { PolymorphicProps } from '@/types/polymorphic';
|
||||
|
||||
import { BottomSheet } from '../bottom_sheet';
|
||||
import { Popover } from '../popover';
|
||||
import type { PopoverProps } from '../popover';
|
||||
|
||||
@@ -65,6 +67,16 @@ export const PopoverMenuCard = <As extends React.ElementType>({
|
||||
className,
|
||||
...props
|
||||
}: PopoverMenuCardProps<As>) => {
|
||||
const isMobile = useBreakpoint('openable');
|
||||
|
||||
if (isMobile && isOpen) {
|
||||
return (
|
||||
<BottomSheet {...props} onClose={onClose}>
|
||||
{children}
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
isOpen={isOpen}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@use '@/styles/mastodon/mixins';
|
||||
@use '@/styles/mastodon/variables';
|
||||
|
||||
.card {
|
||||
@include mixins.elevation-1;
|
||||
@@ -41,6 +42,7 @@
|
||||
|
||||
--cursor: pointer;
|
||||
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: var(--radius-sm);
|
||||
@@ -61,6 +63,10 @@
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
@media (width < variables.$mobile-menu-breakpoint) {
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user