mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-12 21:45:56 -05:00
Redesign: Add 'navigation' type to Menu component (#40222)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import type { Merge } from 'type-fest';
|
||||
@@ -34,10 +36,13 @@ export const MenuCard = <As extends React.ElementType>({
|
||||
{...props}
|
||||
className={classNames(className, classes.card)}
|
||||
data-elevation={elevation}
|
||||
style={{
|
||||
maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth,
|
||||
...style,
|
||||
}}
|
||||
style={
|
||||
{
|
||||
'--_max-card-width':
|
||||
typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth,
|
||||
...style,
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
|
||||
@@ -21,8 +21,8 @@ export const menuItemClass = classes.item;
|
||||
export {
|
||||
MenuItemDivider,
|
||||
MenuItemGroup,
|
||||
MenuItemBase,
|
||||
MenuItem,
|
||||
MenuItemLink,
|
||||
MenuItemRadio,
|
||||
MenuItemCheckbox,
|
||||
} from './items';
|
||||
@@ -36,10 +36,10 @@ interface PopoverState {
|
||||
reference: HTMLButtonElement | null;
|
||||
}
|
||||
|
||||
interface MenuButtonContextProps {
|
||||
interface MenuTriggerContextProps {
|
||||
ref: (button: HTMLButtonElement | null) => void;
|
||||
id: string;
|
||||
'aria-haspopup': 'menu';
|
||||
'aria-haspopup'?: 'menu';
|
||||
'aria-expanded': boolean;
|
||||
'aria-controls'?: string;
|
||||
onKeyDown: React.KeyboardEventHandler<HTMLButtonElement>;
|
||||
@@ -48,16 +48,19 @@ interface MenuButtonContextProps {
|
||||
|
||||
interface MenuListContextProps {
|
||||
ref: (button: HTMLDivElement | null) => void;
|
||||
role: 'menu';
|
||||
role?: 'menu'; // only for menus of type === 'actions'
|
||||
tabIndex: -1;
|
||||
id: string;
|
||||
'aria-labelledby': string;
|
||||
onKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
type MenuType = 'actions' | 'navigation';
|
||||
|
||||
interface MenuState {
|
||||
type: MenuType;
|
||||
popover: PopoverState;
|
||||
menuButtonProps: MenuButtonContextProps;
|
||||
menuTriggerProps: MenuTriggerContextProps;
|
||||
menuListProps: MenuListContextProps;
|
||||
}
|
||||
|
||||
@@ -67,7 +70,7 @@ export function useMenuContext(): MenuState {
|
||||
const context = use(MenuContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useMenu must be used within a <Menu> component');
|
||||
throw new Error('useMenuContext must be used within a <Menu> component');
|
||||
}
|
||||
|
||||
return context;
|
||||
@@ -81,23 +84,36 @@ function getAllMenuItems(menuListElement: HTMLDivElement) {
|
||||
);
|
||||
}
|
||||
|
||||
export const Menu: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
interface MenuProps {
|
||||
/**
|
||||
* Set the type according the the menu's use case for accessible markup.
|
||||
* Use 'navigation' for menus that are primarily used for site navigation.
|
||||
* Note that navigation menus don't support `MenuItemRadio` and `MenuItemCheckbox`.
|
||||
*/
|
||||
type?: MenuType;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Menu: React.FC<MenuProps> = ({ type = 'actions', children }) => {
|
||||
const id = useId();
|
||||
const buttonId = `${id}-button`;
|
||||
const triggerId = `${id}-trigger`;
|
||||
const listId = `${id}-list`;
|
||||
const [buttonElement, setButtonElement] = useState<HTMLButtonElement | null>(
|
||||
null,
|
||||
);
|
||||
const [triggerElement, setTriggerElement] =
|
||||
useState<HTMLButtonElement | null>(null);
|
||||
const [listElement, setListElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const mountListElement = useCallback((element: HTMLDivElement | null) => {
|
||||
setListElement(element);
|
||||
if (element) {
|
||||
const menuItems = getAllMenuItems(element);
|
||||
const elementToFocus = menuItems[0] ?? element;
|
||||
elementToFocus.focus();
|
||||
}
|
||||
}, []);
|
||||
const mountListElement = useCallback(
|
||||
(element: HTMLDivElement | null) => {
|
||||
setListElement(element);
|
||||
|
||||
if (element && type === 'actions') {
|
||||
const menuItems = getAllMenuItems(element);
|
||||
const elementToFocus = menuItems[0] ?? element;
|
||||
elementToFocus.focus();
|
||||
}
|
||||
},
|
||||
[type],
|
||||
);
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
@@ -107,14 +123,19 @@ export const Menu: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
|
||||
const closeMenu = useCallback(() => {
|
||||
setIsMenuOpen(false);
|
||||
buttonElement?.focus();
|
||||
}, [buttonElement]);
|
||||
triggerElement?.focus();
|
||||
}, [triggerElement]);
|
||||
|
||||
const toggleMenu = isMenuOpen ? closeMenu : openMenu;
|
||||
|
||||
const handleMenuNavigation = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLElement>) => {
|
||||
if (!listElement) return;
|
||||
if (!listElement) {
|
||||
if (event.code === 'ArrowDown') {
|
||||
openMenu();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const menuItems = getAllMenuItems(listElement);
|
||||
if (menuItems.length === 0) return;
|
||||
@@ -173,14 +194,16 @@ export const Menu: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
openMenu,
|
||||
closeMenu,
|
||||
toggleMenu,
|
||||
reference: buttonElement,
|
||||
reference: triggerElement,
|
||||
popover: listElement,
|
||||
};
|
||||
|
||||
const menuButtonProps: MenuButtonContextProps = {
|
||||
id: buttonId,
|
||||
ref: setButtonElement,
|
||||
'aria-haspopup': 'menu',
|
||||
const role = type === 'actions' ? 'menu' : undefined;
|
||||
|
||||
const menuTriggerProps: MenuTriggerContextProps = {
|
||||
id: triggerId,
|
||||
ref: setTriggerElement,
|
||||
'aria-haspopup': role,
|
||||
'aria-expanded': isMenuOpen,
|
||||
'aria-controls': listElement ? listId : undefined,
|
||||
onClick: toggleMenu,
|
||||
@@ -190,26 +213,28 @@ export const Menu: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const menuListProps: MenuListContextProps = {
|
||||
id: listId,
|
||||
ref: mountListElement,
|
||||
'aria-labelledby': buttonId,
|
||||
role: 'menu',
|
||||
'aria-labelledby': triggerId,
|
||||
role,
|
||||
tabIndex: -1,
|
||||
onKeyDown: handleMenuNavigation,
|
||||
};
|
||||
|
||||
return {
|
||||
type,
|
||||
popover,
|
||||
menuButtonProps,
|
||||
menuTriggerProps,
|
||||
menuListProps,
|
||||
};
|
||||
}, [
|
||||
type,
|
||||
isMenuOpen,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
toggleMenu,
|
||||
buttonElement,
|
||||
triggerElement,
|
||||
listElement,
|
||||
mountListElement,
|
||||
buttonId,
|
||||
triggerId,
|
||||
listId,
|
||||
handleMenuNavigation,
|
||||
]);
|
||||
@@ -217,22 +242,22 @@ export const Menu: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return <MenuContext value={contextValue}>{children}</MenuContext>;
|
||||
};
|
||||
|
||||
export type MenuButtonProps<As extends React.ElementType> = Merge<
|
||||
export type MenuTriggerProps<As extends React.ElementType> = Merge<
|
||||
React.ComponentProps<As>,
|
||||
{
|
||||
as?: As;
|
||||
}
|
||||
>;
|
||||
|
||||
export const MenuButton = <As extends React.ElementType>({
|
||||
export const MenuTrigger = <As extends React.ElementType>({
|
||||
as: asComp,
|
||||
children,
|
||||
...props
|
||||
}: MenuButtonProps<As>) => {
|
||||
}: MenuTriggerProps<As>) => {
|
||||
const Component = asComp ?? Button;
|
||||
const { menuButtonProps } = useMenuContext();
|
||||
const { menuTriggerProps } = useMenuContext();
|
||||
return (
|
||||
<Component {...props} {...menuButtonProps}>
|
||||
<Component {...props} {...menuTriggerProps}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
@@ -247,7 +272,7 @@ export const MenuList = <As extends React.ElementType>({
|
||||
children,
|
||||
...props
|
||||
}: MenuListProps<As>) => {
|
||||
const { popover, menuListProps } = useMenuContext();
|
||||
const { popover, menuListProps, type } = useMenuContext();
|
||||
|
||||
return (
|
||||
<PopoverMenuCard
|
||||
@@ -259,7 +284,7 @@ export const MenuList = <As extends React.ElementType>({
|
||||
{...props}
|
||||
{...menuListProps}
|
||||
>
|
||||
{children}
|
||||
{type === 'navigation' ? <ul>{children}</ul> : children}
|
||||
</PopoverMenuCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useCallback, useId } from 'react';
|
||||
import { Fragment, useCallback, useId } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import type { NavLinkProps } from 'react-router-dom';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { CheckIcon } from '@phosphor-icons/react';
|
||||
|
||||
@@ -8,6 +10,7 @@ import { Toggle } from '../form_fields/redesign';
|
||||
import { Icon } from '../icon';
|
||||
import type { IconProp } from '../icon';
|
||||
|
||||
import { useMenuContext } from '.';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
interface MenuItemGroupProps extends React.ComponentProps<'div'> {
|
||||
@@ -18,15 +21,19 @@ export const MenuItemGroup: React.FC<MenuItemGroupProps> = ({
|
||||
label,
|
||||
children,
|
||||
}) => {
|
||||
const { type } = useMenuContext();
|
||||
const uniqueId = useId();
|
||||
|
||||
// Use list elements if we're in a navigation menu
|
||||
const Wrapper = type === 'navigation' ? 'li' : 'div';
|
||||
|
||||
return (
|
||||
<div aria-labelledby={uniqueId} role='group'>
|
||||
<Wrapper aria-labelledby={uniqueId} role='group'>
|
||||
<div id={uniqueId} className={classes.itemGroupLabel}>
|
||||
{label}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
{type === 'navigation' ? <ul>{children}</ul> : children}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -46,7 +53,7 @@ type MenuItemProps<As extends React.ElementType> =
|
||||
iconClassName?: string;
|
||||
};
|
||||
|
||||
export const MenuItemBase = <As extends React.ElementType>({
|
||||
const MenuItemBase = <As extends React.ElementType>({
|
||||
active,
|
||||
disabled,
|
||||
as: AsComp,
|
||||
@@ -80,7 +87,9 @@ export const MenuItemBase = <As extends React.ElementType>({
|
||||
|
||||
{children}
|
||||
|
||||
<span className={classes.itemTrailingContent}>{trailingContent}</span>
|
||||
{trailingContent && (
|
||||
<span className={classes.itemTrailingContent}>{trailingContent}</span>
|
||||
)}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
@@ -89,13 +98,77 @@ export const MenuItem: React.FC<Omit<MenuItemProps<'button'>, 'as'>> = ({
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const { type } = useMenuContext();
|
||||
|
||||
const Wrapper = type === 'actions' ? Fragment : 'li';
|
||||
|
||||
return (
|
||||
<MenuItemBase as='button' type='button' role='menuitem' {...props}>
|
||||
{children}
|
||||
</MenuItemBase>
|
||||
<Wrapper>
|
||||
<MenuItemBase
|
||||
as='button'
|
||||
type='button'
|
||||
role={type === 'actions' ? 'menuitem' : undefined}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</MenuItemBase>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
type MenuItemLinkProps = Omit<MenuItemProps<'a'>, 'as'> &
|
||||
(
|
||||
| ({ as: 'a' } & React.ComponentProps<'a'>)
|
||||
| ({ as?: 'link' } & NavLinkProps)
|
||||
);
|
||||
|
||||
export const MenuItemLink: React.FC<MenuItemLinkProps> = ({
|
||||
as,
|
||||
children,
|
||||
onKeyDown,
|
||||
...props
|
||||
}) => {
|
||||
const { type } = useMenuContext();
|
||||
|
||||
const handleSpacebarPress = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLAnchorElement>) => {
|
||||
if (type === 'actions' && e.code === 'Space') {
|
||||
(e.target as HTMLElement).click();
|
||||
e.preventDefault();
|
||||
}
|
||||
onKeyDown?.(e);
|
||||
},
|
||||
[onKeyDown, type],
|
||||
);
|
||||
|
||||
const Wrapper = type === 'actions' ? Fragment : 'li';
|
||||
const asElement = (as ?? 'link') === 'link' ? NavLink : 'a';
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<MenuItemBase
|
||||
as={asElement}
|
||||
role={type === 'actions' ? 'menuitem' : undefined}
|
||||
onKeyDown={handleSpacebarPress}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</MenuItemBase>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
// Helper to prevent item components from being used with incompatible menu types
|
||||
function useAssertMenuType(componentName: string) {
|
||||
const { type } = useMenuContext();
|
||||
|
||||
if (type === 'navigation') {
|
||||
throw new Error(
|
||||
`\`${componentName}\` can not be used inside of \`<Menu type='navigation'>\`. Use \`type='actions'\` instead.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface MenuItemRadioProps extends Omit<
|
||||
MenuItemProps<'button'>,
|
||||
'as' | 'onChange' | 'icon'
|
||||
@@ -112,6 +185,8 @@ export const MenuItemRadio: React.FC<MenuItemRadioProps> = ({
|
||||
onChange,
|
||||
...props
|
||||
}) => {
|
||||
useAssertMenuType('MenuItemRadio');
|
||||
|
||||
const handleChange = useCallback(() => {
|
||||
onChange?.({ value });
|
||||
}, [value, onChange]);
|
||||
@@ -142,6 +217,8 @@ export const MenuItemCheckbox: React.FC<MenuItemCheckboxProps> = ({
|
||||
onChange,
|
||||
...props
|
||||
}) => {
|
||||
useAssertMenuType('MenuItemCheckbox');
|
||||
|
||||
const handleChange = useCallback(() => {
|
||||
onChange?.({ value, checked: !checked });
|
||||
}, [onChange, value, checked]);
|
||||
|
||||
@@ -13,16 +13,16 @@ import { useToggle } from '@/mastodon/hooks/useToggle';
|
||||
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuTrigger,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
MenuItemDivider,
|
||||
MenuItemCheckbox,
|
||||
MenuItemGroup,
|
||||
MenuItemRadio,
|
||||
MenuItemLink,
|
||||
} from '.';
|
||||
import type { MenuCardProps } from './card';
|
||||
import { MenuCard } from './card';
|
||||
|
||||
const meta = {
|
||||
title: 'Redesign/Menu',
|
||||
@@ -43,27 +43,12 @@ type Story = StoryObj<typeof meta>;
|
||||
|
||||
const handleMenuItemClick = action('menu item click');
|
||||
|
||||
export const Simple: Story = {
|
||||
render(args) {
|
||||
return (
|
||||
<MenuCard {...args}>
|
||||
<MenuItem icon={NumberCircleOneIcon} onClick={handleMenuItemClick}>
|
||||
First item
|
||||
</MenuItem>
|
||||
<MenuItem icon={NumberCircleTwoIcon} onClick={handleMenuItemClick}>
|
||||
Second item
|
||||
</MenuItem>
|
||||
</MenuCard>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Popover: Story = {
|
||||
export const Default: Story = {
|
||||
render(args) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<Menu>
|
||||
<MenuButton>Click to show dropdown</MenuButton>
|
||||
<MenuTrigger>Show actions</MenuTrigger>
|
||||
|
||||
<MenuList {...args}>
|
||||
<MenuItem icon={NumberCircleOneIcon} onClick={handleMenuItemClick}>
|
||||
@@ -89,44 +74,70 @@ export const Complex: Story = {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MenuCard {...args}>
|
||||
<MenuItem onClick={handleMenuItemClick}>First item</MenuItem>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<Menu>
|
||||
<MenuTrigger>World settings</MenuTrigger>
|
||||
|
||||
<MenuItemDivider />
|
||||
<MenuList {...args}>
|
||||
<MenuItem onClick={handleMenuItemClick}>First item</MenuItem>
|
||||
|
||||
<MenuItemCheckbox
|
||||
icon={sun ? SunIcon : MoonIcon}
|
||||
checked={sun}
|
||||
onChange={onToggle}
|
||||
value='daytime'
|
||||
>
|
||||
Daytime toggle
|
||||
</MenuItemCheckbox>
|
||||
<MenuItemDivider />
|
||||
<MenuItemGroup label='Precipitation'>
|
||||
<MenuItemRadio
|
||||
value='none'
|
||||
checked={precip === 'none'}
|
||||
onChange={handlePrecipChange}
|
||||
>
|
||||
None
|
||||
</MenuItemRadio>
|
||||
<MenuItemRadio
|
||||
value='rain'
|
||||
checked={precip === 'rain'}
|
||||
onChange={handlePrecipChange}
|
||||
>
|
||||
Rain
|
||||
</MenuItemRadio>
|
||||
<MenuItemRadio
|
||||
value='snow'
|
||||
checked={precip === 'snow'}
|
||||
onChange={handlePrecipChange}
|
||||
>
|
||||
Snow
|
||||
</MenuItemRadio>
|
||||
</MenuItemGroup>
|
||||
</MenuCard>
|
||||
<MenuItemDivider />
|
||||
|
||||
<MenuItemCheckbox
|
||||
icon={sun ? SunIcon : MoonIcon}
|
||||
checked={sun}
|
||||
onChange={onToggle}
|
||||
value='daytime'
|
||||
>
|
||||
Daytime toggle
|
||||
</MenuItemCheckbox>
|
||||
<MenuItemDivider />
|
||||
<MenuItemGroup label='Precipitation'>
|
||||
<MenuItemRadio
|
||||
value='none'
|
||||
checked={precip === 'none'}
|
||||
onChange={handlePrecipChange}
|
||||
>
|
||||
None
|
||||
</MenuItemRadio>
|
||||
<MenuItemRadio
|
||||
value='rain'
|
||||
checked={precip === 'rain'}
|
||||
onChange={handlePrecipChange}
|
||||
>
|
||||
Rain
|
||||
</MenuItemRadio>
|
||||
<MenuItemRadio
|
||||
value='snow'
|
||||
checked={precip === 'snow'}
|
||||
onChange={handlePrecipChange}
|
||||
>
|
||||
Snow
|
||||
</MenuItemRadio>
|
||||
</MenuItemGroup>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Navigation: Story = {
|
||||
render(args) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<Menu type='navigation'>
|
||||
<MenuTrigger>More links</MenuTrigger>
|
||||
|
||||
<MenuList {...args}>
|
||||
<MenuItemLink to='/about'>About</MenuItemLink>
|
||||
<MenuItemLink to='/privacy'>Terms & Conditions</MenuItemLink>
|
||||
<MenuItemLink href='/terms' as='a'>
|
||||
Privacy
|
||||
</MenuItemLink>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
background: var(--color-bg-primary);
|
||||
overflow: hidden;
|
||||
z-index: calc(infinity);
|
||||
max-width: var(--_max-card-width, 100vw);
|
||||
|
||||
&[data-elevation='2'] {
|
||||
@include mixins.elevation-2;
|
||||
@@ -44,6 +45,7 @@
|
||||
border-radius: var(--radius-sm);
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
text-decoration: none;
|
||||
cursor: var(--cursor);
|
||||
transition:
|
||||
background 200ms,
|
||||
@@ -57,10 +59,19 @@
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:hover:not([aria-disabled='true']) {
|
||||
background-color: var(--color-bg-highlight);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
// Override overeager global styles
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--outline-focus-default);
|
||||
outline-offset: -2px;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IconButton } from '@/mastodon/components/button/redesign';
|
||||
import { CircularProgress } from '@/mastodon/components/circular_progress';
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuTrigger,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
} from '@/mastodon/components/menu';
|
||||
@@ -71,7 +71,7 @@ export const ComposeRedesignButton: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
<MenuTrigger
|
||||
as={IconButton}
|
||||
icon={PenNibIcon}
|
||||
color='neutral'
|
||||
@@ -82,7 +82,7 @@ export const ComposeRedesignButton: React.FC = () => {
|
||||
id='compose.new'
|
||||
defaultMessage='Write a new post or messsage'
|
||||
/>
|
||||
</MenuButton>
|
||||
</MenuTrigger>
|
||||
|
||||
<MenuList maxWidth={180} placement='top-end'>
|
||||
<MenuItem name='post' onClick={handleComposerOpen} icon={NewspaperIcon}>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Blurhash } from '@/mastodon/components/blurhash';
|
||||
import { IconButton } from '@/mastodon/components/button/redesign';
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuTrigger,
|
||||
MenuItem,
|
||||
MenuItemDivider,
|
||||
MenuList,
|
||||
@@ -73,7 +73,7 @@ export const ComposeUpload: React.FC<{
|
||||
)}
|
||||
|
||||
<Menu>
|
||||
<MenuButton
|
||||
<MenuTrigger
|
||||
as={IconButton}
|
||||
icon={DotsThreeIcon}
|
||||
size='sm'
|
||||
@@ -84,7 +84,7 @@ export const ComposeUpload: React.FC<{
|
||||
id='compose.upload.menu'
|
||||
defaultMessage='Add alt text or remove the image'
|
||||
/>
|
||||
</MenuButton>
|
||||
</MenuTrigger>
|
||||
|
||||
<ComposeUploadMenu attachment={attachment} single={single} />
|
||||
</Menu>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { DisplayNameSimple } from '@/mastodon/components/display_name/simple';
|
||||
import {
|
||||
Menu,
|
||||
MenuList,
|
||||
MenuButton,
|
||||
MenuTrigger,
|
||||
MenuItemDivider,
|
||||
MenuItemGroup,
|
||||
MenuItem,
|
||||
@@ -47,9 +47,9 @@ export const ComposeVisibility: React.FC<{ className?: string }> = ({
|
||||
description='Before button that indicates who a post is for (Public, Followers, mentioned people)'
|
||||
/>
|
||||
<Menu>
|
||||
<MenuButton size='sm'>
|
||||
<MenuTrigger size='sm'>
|
||||
<ComposeVisibilityButtonText privacy={privacy} />
|
||||
</MenuButton>
|
||||
</MenuTrigger>
|
||||
|
||||
{privacy !== 'direct' ? (
|
||||
<ComposeVisibilityMenu />
|
||||
|
||||
Reference in New Issue
Block a user