mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
feat: enhance gallery page header with responsive social media links and view mode options
- Updated `PageHeaderLeft` to conditionally display social media links on larger screens. - Introduced `MoreActionMenu` in `PageHeaderRight` for mobile users, allowing access to view mode settings and social links. - Modified `ViewModeSegment` to ensure consistent display across different screen sizes. - Enhanced `DropdownMenuItem` to support child components for better flexibility in dropdown menus. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -43,7 +43,7 @@ export const PageHeaderLeft = () => {
|
||||
<span className="text-xs text-white/40 lg:text-sm">{visiblePhotoCount}</span>
|
||||
</div>
|
||||
{(githubUrl || twitterUrl || hasRss) && (
|
||||
<div className="ml-1 flex items-center gap-1 border-l border-white/10 pl-2">
|
||||
<div className="ml-1 hidden items-center gap-1 border-l border-white/10 pl-2 lg:flex">
|
||||
{githubUrl && <SocialIconButton icon="i-mingcute-github-fill" title="GitHub" href={githubUrl} />}
|
||||
{twitterUrl && <SocialIconButton icon="i-mingcute-twitter-fill" title="Twitter" href={twitterUrl} />}
|
||||
{hasRss && <SocialIconButton icon="i-mingcute-rss-2-fill" title="RSS" href="/feed.xml" />}
|
||||
|
||||
@@ -14,13 +14,13 @@ import { Drawer } from 'vaul'
|
||||
|
||||
import { gallerySettingAtom, isCommandPaletteOpenAtom } from '~/atoms/app'
|
||||
import { sessionUserAtom } from '~/atoms/session'
|
||||
import { injectConfig } from '~/config'
|
||||
import { injectConfig, siteConfig } from '~/config'
|
||||
import { useMobile } from '~/hooks/useMobile'
|
||||
import { authApi } from '~/lib/api/auth'
|
||||
|
||||
import { UserAvatar } from '../../social/comments/UserAvatar'
|
||||
import { ViewPanel } from '../panels/ViewPanel'
|
||||
import { ActionIconButton } from './utils'
|
||||
import { ActionIconButton, resolveSocialUrl } from './utils'
|
||||
|
||||
export const PageHeaderRight = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -77,6 +77,8 @@ export const PageHeaderRight = () => {
|
||||
<ViewPanel />
|
||||
</DesktopViewButton>
|
||||
)}
|
||||
|
||||
{isMobile && <MoreActionMenu />}
|
||||
</div>
|
||||
|
||||
{/* Auth Section - Only show when useCloud is true */}
|
||||
@@ -89,6 +91,84 @@ export const PageHeaderRight = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const MoreActionMenu = () => {
|
||||
const { t } = useTranslation()
|
||||
const [settings, setSettings] = useAtom(gallerySettingAtom)
|
||||
|
||||
const githubUrl =
|
||||
siteConfig.social && siteConfig.social.github
|
||||
? resolveSocialUrl(siteConfig.social.github, { baseUrl: 'https://github.com/' })
|
||||
: undefined
|
||||
const twitterUrl =
|
||||
siteConfig.social && siteConfig.social.twitter
|
||||
? resolveSocialUrl(siteConfig.social.twitter, { baseUrl: 'https://twitter.com/', stripAt: true })
|
||||
: undefined
|
||||
const hasRss = true
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="relative flex size-7 items-center justify-center rounded text-white/60 transition-all duration-200 hover:bg-white/10 hover:text-white lg:hidden"
|
||||
>
|
||||
<i className="i-mingcute-more-2-line text-lg" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="min-w-[180px]">
|
||||
<div className="px-2 py-1.5 text-xs font-medium text-white/50">{t('action.view.title')}</div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => setSettings((prev) => ({ ...prev, viewMode: 'masonry' }))}
|
||||
className="justify-between"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<i className="i-mingcute-grid-line text-base" />
|
||||
{t('gallery.view.masonry')}
|
||||
</span>
|
||||
{settings.viewMode === 'masonry' && <i className="i-mingcute-check-line text-base" />}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => setSettings((prev) => ({ ...prev, viewMode: 'list' }))}
|
||||
className="justify-between"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<i className="i-mingcute-list-ordered-line text-base" />
|
||||
{t('gallery.view.list')}
|
||||
</span>
|
||||
{settings.viewMode === 'list' && <i className="i-mingcute-check-line text-base" />}
|
||||
</DropdownMenuItem>
|
||||
|
||||
{(githubUrl || twitterUrl || hasRss) && <DropdownMenuSeparator />}
|
||||
|
||||
{githubUrl && (
|
||||
<DropdownMenuItem asChild>
|
||||
<a href={githubUrl} target="_blank" rel="noreferrer" className="flex items-center gap-2">
|
||||
<i className="i-mingcute-github-fill text-base" />
|
||||
GitHub
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{twitterUrl && (
|
||||
<DropdownMenuItem asChild>
|
||||
<a href={twitterUrl} target="_blank" rel="noreferrer" className="flex items-center gap-2">
|
||||
<i className="i-mingcute-twitter-fill text-base" />
|
||||
Twitter
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{hasRss && (
|
||||
<DropdownMenuItem asChild>
|
||||
<a href="/feed.xml" target="_blank" rel="noreferrer" className="flex items-center gap-2">
|
||||
<i className="i-mingcute-rss-2-fill text-base" />
|
||||
RSS
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
// 紧凑版本的桌面端视图按钮
|
||||
const DesktopViewButton = ({
|
||||
icon,
|
||||
|
||||
@@ -15,7 +15,7 @@ export const ViewModeSegment = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-material-medium/40 relative flex h-7 items-center gap-0.5 rounded-lg p-0.5 lg:h-8 lg:gap-1 lg:p-1">
|
||||
<div className="bg-material-medium/40 relative hidden h-7 items-center gap-0.5 rounded-lg p-0.5 lg:flex lg:h-8 lg:gap-1 lg:p-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleViewModeChange('masonry')}
|
||||
|
||||
@@ -78,6 +78,8 @@ const DropdownMenuItem = ({
|
||||
active,
|
||||
highlightColor: _highlightColor = 'accent',
|
||||
shortcut: _shortcut,
|
||||
asChild,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
@@ -87,35 +89,57 @@ const DropdownMenuItem = ({
|
||||
shortcut?: string
|
||||
} & {
|
||||
ref?: React.Ref<React.ElementRef<typeof DropdownMenuPrimitive.Item> | null>
|
||||
}) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={clsxm(
|
||||
'cursor-menu relative flex select-none items-center rounded-lg px-2.5 py-1 outline-none data-disabled:pointer-events-none data-disabled:opacity-50',
|
||||
'focus-within:outline-transparent text-sm my-0.5 transition-all duration-200',
|
||||
'data-highlighted:text-accent',
|
||||
'h-[28px]',
|
||||
inset && 'pl-8',
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
// @ts-ignore - CSS variable for data-highlighted state
|
||||
'--highlight-bg':
|
||||
'linear-gradient(to right, color-mix(in srgb, var(--color-accent) 8%, transparent), color-mix(in srgb, var(--color-accent) 5%, transparent))',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{!!icon && (
|
||||
<span className="mr-1.5 inline-flex size-4 items-center justify-center">
|
||||
{typeof icon === 'function' ? icon({ isActive: active }) : icon}
|
||||
</span>
|
||||
)}
|
||||
{props.children}
|
||||
}) => {
|
||||
const mergedClassName = clsxm(
|
||||
'cursor-menu relative flex select-none items-center rounded-lg px-2.5 py-1 outline-none data-disabled:pointer-events-none data-disabled:opacity-50',
|
||||
'focus-within:outline-transparent text-sm my-0.5 transition-all duration-200',
|
||||
'data-highlighted:text-accent',
|
||||
'h-[28px]',
|
||||
inset && 'pl-8',
|
||||
className,
|
||||
)
|
||||
|
||||
{/* Justify Fill */}
|
||||
{!!icon && <span className="ml-1.5 size-4" />}
|
||||
</DropdownMenuPrimitive.Item>
|
||||
)
|
||||
if (asChild) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={mergedClassName}
|
||||
style={{
|
||||
// @ts-ignore - CSS variable for data-highlighted state
|
||||
'--highlight-bg':
|
||||
'linear-gradient(to right, color-mix(in srgb, var(--color-accent) 8%, transparent), color-mix(in srgb, var(--color-accent) 5%, transparent))',
|
||||
}}
|
||||
asChild
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={mergedClassName}
|
||||
style={{
|
||||
// @ts-ignore - CSS variable for data-highlighted state
|
||||
'--highlight-bg':
|
||||
'linear-gradient(to right, color-mix(in srgb, var(--color-accent) 8%, transparent), color-mix(in srgb, var(--color-accent) 5%, transparent))',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{!!icon && (
|
||||
<span className="mr-1.5 inline-flex size-4 items-center justify-center">
|
||||
{typeof icon === 'function' ? icon({ isActive: active }) : icon}
|
||||
</span>
|
||||
)}
|
||||
{children}
|
||||
|
||||
{/* Justify Fill */}
|
||||
{!!icon && <span className="ml-1.5 size-4" />}
|
||||
</DropdownMenuPrimitive.Item>
|
||||
)
|
||||
}
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = ({
|
||||
|
||||
Reference in New Issue
Block a user