feat(header): 重构用户头像展示为用户菜单

This commit is contained in:
zyh 2024-10-22 07:18:29 +00:00
parent 7018ede113
commit df93f0d897
4 changed files with 200 additions and 7 deletions

View File

@ -8,13 +8,13 @@ import { useState } from 'react';
import { LoginDialog } from '~/components/auth/LoginDialog';
import { RegisterDialog } from '~/components/auth/RegisterDialog';
import { useAuth } from '~/hooks/useAuth';
import { Avatar } from '~/components/ui/Avatar';
import { UserMenu } from './UserMenu';
export function Header() {
const chat = useStore(chatStore);
const [isLoginOpen, setIsLoginOpen] = useState(false);
const [isRegisterOpen, setIsRegisterOpen] = useState(false);
const { isAuthenticated, user } = useAuth();
const { isAuthenticated } = useAuth();
return (
<header
@ -45,11 +45,8 @@ export function Header() {
)}
</ClientOnly>
)}
{isAuthenticated && user ? (
<div className="flex items-center gap-2">
<Avatar src={user.avatarUrl} alt={user.nickname} />
<span className="text-sm font-medium text-bolt-elements-textPrimary">{user.nickname}</span>
</div>
{isAuthenticated ? (
<UserMenu />
) : (
<>
<button

View File

@ -0,0 +1,74 @@
import { Menu, Transition } from '@headlessui/react';
import { Fragment } from 'react';
import { useAuth } from '~/hooks/useAuth';
import { Avatar } from '~/components/ui/Avatar';
import { Link } from '@remix-run/react';
export function UserMenu() {
const { user, logout } = useAuth();
if (!user) return null;
return (
<Menu as="div" className="relative inline-block text-left">
<div>
<Menu.Button className="inline-flex items-center gap-x-1.5 rounded-md bg-bolt-elements-background-depth-2 px-3 py-2 text-sm font-medium text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-primary-background">
<Avatar src={user.avatarUrl} alt={user.nickname} />
<span className="ml-2">{user.nickname}</span>
<div className="i-ph:caret-down-fill text-bolt-elements-textSecondary" aria-hidden="true" />
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-bolt-elements-background-depth-2 shadow-lg ring-1 ring-bolt-elements-borderColor focus:outline-none">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<Link
to="/profile"
className={`${
active ? 'bg-bolt-elements-button-primary-background text-bolt-elements-button-primary-text' : 'text-bolt-elements-textSecondary'
} block px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
>
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<Link
to="/subscription"
className={`${
active ? 'bg-bolt-elements-button-primary-background text-bolt-elements-button-primary-text' : 'text-bolt-elements-textSecondary'
} block px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
>
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
onClick={logout}
className={`${
active ? 'bg-bolt-elements-button-primary-background text-bolt-elements-button-primary-text' : 'text-bolt-elements-textSecondary'
} block w-full text-left px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
>
退
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
);
}

View File

@ -44,6 +44,7 @@
"@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.28.4",
"@headlessui/react": "^2.1.10",
"@iconify-json/ph": "^1.1.13",
"@iconify-json/svg-spinners": "^1.1.2",
"@lezer/highlight": "^1.2.0",

View File

@ -59,6 +59,9 @@ dependencies:
'@codemirror/view':
specifier: ^6.28.4
version: 6.34.1
'@headlessui/react':
specifier: ^2.1.10
version: 2.1.10(react-dom@18.3.1)(react@18.3.1)
'@iconify-json/ph':
specifier: ^1.1.13
version: 1.2.1
@ -1955,10 +1958,38 @@ packages:
react-dom: 18.3.1(react@18.3.1)
dev: false
/@floating-ui/react@0.26.25(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-hZOmgN0NTOzOuZxI1oIrDu3Gcl8WViIkvPMpB4xdd4QD6xAMtwgwr3VPoiyH/bLtRcS1cDnhxLSD1NsMJmwh/A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@floating-ui/react-dom': 2.1.2(react-dom@18.3.1)(react@18.3.1)
'@floating-ui/utils': 0.2.8
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tabbable: 6.2.0
dev: false
/@floating-ui/utils@0.2.8:
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
dev: false
/@headlessui/react@2.1.10(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-6mLa2fjMDAFQi+/R10B+zU3edsUk/MDtENB2zHho0lqKU1uzhAfJLUduWds4nCo8wbl3vULtC5rJfZAQ1yqIng==}
engines: {node: '>=10'}
peerDependencies:
react: ^18
react-dom: ^18
dependencies:
'@floating-ui/react': 0.26.25(react-dom@18.3.1)(react@18.3.1)
'@react-aria/focus': 3.18.4(react@18.3.1)
'@react-aria/interactions': 3.22.4(react@18.3.1)
'@tanstack/react-virtual': 3.10.8(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@humanfs/core@0.19.0:
resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==}
engines: {node: '>=18.18.0'}
@ -2747,6 +2778,71 @@ packages:
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
dev: false
/@react-aria/focus@3.18.4(react@18.3.1):
resolution: {integrity: sha512-91J35077w9UNaMK1cpMUEFRkNNz0uZjnSwiyBCFuRdaVuivO53wNC9XtWSDNDdcO5cGy87vfJRVAiyoCn/mjqA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@react-aria/interactions': 3.22.4(react@18.3.1)
'@react-aria/utils': 3.25.3(react@18.3.1)
'@react-types/shared': 3.25.0(react@18.3.1)
'@swc/helpers': 0.5.13
clsx: 2.1.1
react: 18.3.1
dev: false
/@react-aria/interactions@3.22.4(react@18.3.1):
resolution: {integrity: sha512-E0vsgtpItmknq/MJELqYJwib+YN18Qag8nroqwjk1qOnBa9ROIkUhWJerLi1qs5diXq9LHKehZDXRlwPvdEFww==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@react-aria/ssr': 3.9.6(react@18.3.1)
'@react-aria/utils': 3.25.3(react@18.3.1)
'@react-types/shared': 3.25.0(react@18.3.1)
'@swc/helpers': 0.5.13
react: 18.3.1
dev: false
/@react-aria/ssr@3.9.6(react@18.3.1):
resolution: {integrity: sha512-iLo82l82ilMiVGy342SELjshuWottlb5+VefO3jOQqQRNYnJBFpUSadswDPbRimSgJUZuFwIEYs6AabkP038fA==}
engines: {node: '>= 12'}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@swc/helpers': 0.5.13
react: 18.3.1
dev: false
/@react-aria/utils@3.25.3(react@18.3.1):
resolution: {integrity: sha512-PR5H/2vaD8fSq0H/UB9inNbc8KDcVmW6fYAfSWkkn+OAdhTTMVKqXXrZuZBWyFfSD5Ze7VN6acr4hrOQm2bmrA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@react-aria/ssr': 3.9.6(react@18.3.1)
'@react-stately/utils': 3.10.4(react@18.3.1)
'@react-types/shared': 3.25.0(react@18.3.1)
'@swc/helpers': 0.5.13
clsx: 2.1.1
react: 18.3.1
dev: false
/@react-stately/utils@3.10.4(react@18.3.1):
resolution: {integrity: sha512-gBEQEIMRh5f60KCm7QKQ2WfvhB2gLUr9b72sqUdIZ2EG+xuPgaIlCBeSicvjmjBvYZwOjoOEnmIkcx2GHp/HWw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@swc/helpers': 0.5.13
react: 18.3.1
dev: false
/@react-types/shared@3.25.0(react@18.3.1):
resolution: {integrity: sha512-OZSyhzU6vTdW3eV/mz5i6hQwQUhkRs7xwY2d1aqPvTdMe0+2cY7Fwp45PAiwYLEj73i9ro2FxF9qC4DvHGSCgQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
react: 18.3.1
dev: false
/@remix-run/cloudflare-pages@2.13.1(@cloudflare/workers-types@4.20241018.0)(typescript@5.6.3):
resolution: {integrity: sha512-CcXcGkFx7RJ5yROQXvyzBg1AR6Emv7uOZWv+PbwNRdtsB8lsmiTSrrmpxNlg3FFxX2Khul6B5/V8hpohhl3KJw==}
engines: {node: '>=18.0.0'}
@ -3174,6 +3270,27 @@ packages:
- typescript
dev: true
/@swc/helpers@0.5.13:
resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==}
dependencies:
tslib: 2.8.0
dev: false
/@tanstack/react-virtual@3.10.8(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@tanstack/virtual-core': 3.10.8
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@tanstack/virtual-core@3.10.8:
resolution: {integrity: sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==}
dev: false
/@types/acorn@4.0.6:
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
dependencies:
@ -9738,6 +9855,10 @@ packages:
tslib: 2.8.0
dev: true
/tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
dev: false
/tar-fs@2.1.1:
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
dependencies: