ProfileDropdown: refactor with Floating UI

This commit is contained in:
Alex Gleason 2023-02-06 16:33:08 -06:00
parent aa8f84d352
commit 7f6b19aa4a
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
3 changed files with 113 additions and 32 deletions

View file

@ -1,11 +1,13 @@
import { useFloating } from '@floating-ui/react';
import clsx from 'clsx';
import throttle from 'lodash/throttle';
import React from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { fetchOwnAccounts, logOut, switchAccount } from 'soapbox/actions/auth';
import Account from 'soapbox/components/account';
import { Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList } from 'soapbox/components/ui';
import { MenuDivider } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';
@ -39,6 +41,8 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
const features = useFeatures();
const intl = useIntl();
const [visible, setVisible] = useState(false);
const { x, y, strategy, refs } = useFloating({ placement: 'bottom-end' });
const authUsers = useAppSelector((state) => state.auth.users);
const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.id)!));
@ -62,7 +66,7 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
);
};
const menu: IMenuItem[] = React.useMemo(() => {
const menu: IMenuItem[] = useMemo(() => {
const menu: IMenuItem[] = [];
menu.push({ text: renderAccount(account), to: `/@${account.acct}` });
@ -96,42 +100,78 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
return menu;
}, [account, authUsers, features]);
React.useEffect(() => {
const toggleVisible = () => setVisible(!visible);
useEffect(() => {
fetchOwnAccountThrottled();
}, [account, authUsers]);
return (
<Menu>
<MenuButton>
<>
<button type='button' ref={refs.setReference} onClick={toggleVisible}>
{children}
</MenuButton>
</button>
<MenuList>
{menu.map((menuItem, idx) => {
if (menuItem.toggle) {
return (
<div key={idx} className='flex flex-row items-center justify-between space-x-4 px-4 py-1 text-sm text-gray-700 dark:text-gray-400'>
<span>{menuItem.text}</span>
{menuItem.toggle}
</div>
);
} else if (!menuItem.text) {
return <MenuDivider key={idx} />;
} else {
const Comp: any = menuItem.action ? MenuItem : MenuLink;
const itemProps = menuItem.action ? { onSelect: menuItem.action } : { to: menuItem.to, as: Link };
return (
<Comp key={idx} {...itemProps} className='truncate'>
{menuItem.text}
</Comp>
);
}
})}
</MenuList>
</Menu>
{visible && (
<div
ref={refs.setFloating}
className='z-[1003] mt-2 rounded-md bg-white shadow-lg focus:outline-none dark:bg-gray-900 dark:ring-2 dark:ring-primary-700'
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
width: 'max-content',
}}
>
{menu.map((menuItem, i) => (
<MenuItem key={i} menuItem={menuItem} />
))}
</div>
)}
</>
);
};
interface MenuItemProps {
className?: string
menuItem: IMenuItem
}
const MenuItem: React.FC<MenuItemProps> = ({ className, menuItem }) => {
const baseClassName = clsx(className, 'block cursor-pointer truncate px-4 py-2.5 text-sm text-gray-700 dark:text-gray-500');
if (menuItem.toggle) {
return (
<div className='flex flex-row items-center justify-between space-x-4 px-4 py-1 text-sm text-gray-700 dark:text-gray-400'>
<span>{menuItem.text}</span>
{menuItem.toggle}
</div>
);
} else if (!menuItem.text) {
return <MenuDivider />;
} else if (menuItem.action) {
return (
<button
type='button'
onClick={menuItem.action}
className={baseClassName}
>
{menuItem.text}
</button>
);
} else if (menuItem.to) {
return (
<Link
to={menuItem.to}
className={clsx(baseClassName, 'hover:bg-gray-100 dark:hover:bg-gray-800')}
>
{menuItem.text}
</Link>
);
} else {
throw menuItem;
}
};
export default ProfileDropdown;

View file

@ -46,6 +46,7 @@
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/runtime": "^7.20.13",
"@floating-ui/react": "^0.19.1",
"@fontsource/inter": "^4.5.1",
"@fontsource/roboto-mono": "^4.5.8",
"@gamestdio/websocket": "^0.3.2",

View file

@ -1722,6 +1722,34 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@floating-ui/core@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.0.tgz#ae7ae7923d41f3d84cb2fd88740a89436610bbec"
integrity sha512-GHUXPEhMEmTpnpIfesFA2KAoMJPb1SPQw964tToQwt+BbGXdhqTCWT1rOb0VURGylsxsYxiGMnseJ3IlclVpVA==
"@floating-ui/dom@^1.1.1":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.0.tgz#a60212069cc58961c478037c30eba4b191c75316"
integrity sha512-QXzg57o1cjLz3cGETzKXjI3kx1xyS49DW9l7kV2jw2c8Yftd434t2hllX0sVGn2Q8MtcW/4pNm8bfE1/4n6mng==
dependencies:
"@floating-ui/core" "^1.2.0"
"@floating-ui/react-dom@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.2.2.tgz#ed256992fd44fcfcddc96da68b4b92f123d61871"
integrity sha512-DbmFBLwFrZhtXgCI2ra7wXYT8L2BN4/4AMQKyu05qzsVji51tXOfF36VE2gpMB6nhJGHa85PdEg75FB4+vnLFQ==
dependencies:
"@floating-ui/dom" "^1.1.1"
"@floating-ui/react@^0.19.1":
version "0.19.1"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.19.1.tgz#bcaeaf3856dfeea388816f7e66750cab26208376"
integrity sha512-h7hr53rLp+VVvWvbu0dOBvGsLeeZwn1DTLIllIaLYjGWw20YhAgEqegHU+nc7BJ30ttxq4Sq6hqARm0ne6chXQ==
dependencies:
"@floating-ui/react-dom" "^1.2.2"
aria-hidden "^1.1.3"
tabbable "^6.0.1"
"@fontsource/inter@^4.5.1":
version "4.5.1"
resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-4.5.1.tgz#058d8a02354f3c78e369d452c15d33557ec1b705"
@ -5381,6 +5409,13 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-hidden@^1.1.3:
version "1.2.2"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.2.tgz#8c4f7cc88d73ca42114106fdf6f47e68d31475b8"
integrity sha512-6y/ogyDTk/7YAe91T3E2PR1ALVKyM2QbTio5HwM+N1Q6CMlCKhvClyIjkckBswa0f2xJhjsfzIGa1yVSe1UMVA==
dependencies:
tslib "^2.0.0"
aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
@ -16505,6 +16540,11 @@ tabbable@^5.3.3:
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.3.3.tgz#aac0ff88c73b22d6c3c5a50b1586310006b47fbf"
integrity sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==
tabbable@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.0.1.tgz#427a09b13c83ae41eed3e88abb76a4af28bde1a6"
integrity sha512-SYJSIgeyXW7EuX1ytdneO5e8jip42oHWg9xl/o3oTYhmXusZVgiA+VlPvjIN+kHii9v90AmzTZEBcsEvuAY+TA==
table@^6.8.1:
version "6.8.1"
resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf"