mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
feat(header): 重构用户头像展示为用户菜单
This commit is contained in:
parent
7018ede113
commit
df93f0d897
@ -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
|
||||
|
||||
74
app/components/header/UserMenu.tsx
Normal file
74
app/components/header/UserMenu.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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",
|
||||
|
||||
121
pnpm-lock.yaml
121
pnpm-lock.yaml
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user