mirror of
https://github.com/Afilmory/afilmory
synced 2026-04-24 23:05:05 +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>
|
<span className="text-xs text-white/40 lg:text-sm">{visiblePhotoCount}</span>
|
||||||
</div>
|
</div>
|
||||||
{(githubUrl || twitterUrl || hasRss) && (
|
{(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} />}
|
{githubUrl && <SocialIconButton icon="i-mingcute-github-fill" title="GitHub" href={githubUrl} />}
|
||||||
{twitterUrl && <SocialIconButton icon="i-mingcute-twitter-fill" title="Twitter" href={twitterUrl} />}
|
{twitterUrl && <SocialIconButton icon="i-mingcute-twitter-fill" title="Twitter" href={twitterUrl} />}
|
||||||
{hasRss && <SocialIconButton icon="i-mingcute-rss-2-fill" title="RSS" href="/feed.xml" />}
|
{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 { gallerySettingAtom, isCommandPaletteOpenAtom } from '~/atoms/app'
|
||||||
import { sessionUserAtom } from '~/atoms/session'
|
import { sessionUserAtom } from '~/atoms/session'
|
||||||
import { injectConfig } from '~/config'
|
import { injectConfig, siteConfig } from '~/config'
|
||||||
import { useMobile } from '~/hooks/useMobile'
|
import { useMobile } from '~/hooks/useMobile'
|
||||||
import { authApi } from '~/lib/api/auth'
|
import { authApi } from '~/lib/api/auth'
|
||||||
|
|
||||||
import { UserAvatar } from '../../social/comments/UserAvatar'
|
import { UserAvatar } from '../../social/comments/UserAvatar'
|
||||||
import { ViewPanel } from '../panels/ViewPanel'
|
import { ViewPanel } from '../panels/ViewPanel'
|
||||||
import { ActionIconButton } from './utils'
|
import { ActionIconButton, resolveSocialUrl } from './utils'
|
||||||
|
|
||||||
export const PageHeaderRight = () => {
|
export const PageHeaderRight = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -77,6 +77,8 @@ export const PageHeaderRight = () => {
|
|||||||
<ViewPanel />
|
<ViewPanel />
|
||||||
</DesktopViewButton>
|
</DesktopViewButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isMobile && <MoreActionMenu />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Auth Section - Only show when useCloud is true */}
|
{/* 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 = ({
|
const DesktopViewButton = ({
|
||||||
icon,
|
icon,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const ViewModeSegment = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleViewModeChange('masonry')}
|
onClick={() => handleViewModeChange('masonry')}
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ const DropdownMenuItem = ({
|
|||||||
active,
|
active,
|
||||||
highlightColor: _highlightColor = 'accent',
|
highlightColor: _highlightColor = 'accent',
|
||||||
shortcut: _shortcut,
|
shortcut: _shortcut,
|
||||||
|
asChild,
|
||||||
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
}: React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||||
inset?: boolean
|
inset?: boolean
|
||||||
@@ -87,35 +89,57 @@ const DropdownMenuItem = ({
|
|||||||
shortcut?: string
|
shortcut?: string
|
||||||
} & {
|
} & {
|
||||||
ref?: React.Ref<React.ElementRef<typeof DropdownMenuPrimitive.Item> | null>
|
ref?: React.Ref<React.ElementRef<typeof DropdownMenuPrimitive.Item> | null>
|
||||||
}) => (
|
}) => {
|
||||||
<DropdownMenuPrimitive.Item
|
const mergedClassName = clsxm(
|
||||||
ref={ref}
|
'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',
|
||||||
className={clsxm(
|
'focus-within:outline-transparent text-sm my-0.5 transition-all duration-200',
|
||||||
'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',
|
'data-highlighted:text-accent',
|
||||||
'focus-within:outline-transparent text-sm my-0.5 transition-all duration-200',
|
'h-[28px]',
|
||||||
'data-highlighted:text-accent',
|
inset && 'pl-8',
|
||||||
'h-[28px]',
|
className,
|
||||||
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}
|
|
||||||
|
|
||||||
{/* Justify Fill */}
|
if (asChild) {
|
||||||
{!!icon && <span className="ml-1.5 size-4" />}
|
return (
|
||||||
</DropdownMenuPrimitive.Item>
|
<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
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||||
|
|
||||||
const DropdownMenuCheckboxItem = ({
|
const DropdownMenuCheckboxItem = ({
|
||||||
|
|||||||
Reference in New Issue
Block a user