Refactor GalleryPanel and GalleryBrowser for improved layout and functionality
Updated the GalleryPanel component to ensure it occupies the full screen height, enhancing the user experience by preventing dual scrollbars. Modified the GalleryBrowser to support a new 'fill' prop, allowing it to adapt its layout based on the context, and adjusted the internal scrolling behavior accordingly. These changes improve the overall usability and visual consistency of the image gallery interface.
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { GalleryBrowser } from './ImageGallery'
|
||||
|
||||
/** Отдельная страница «Галерея»: просмотр/загрузка/удаление всех изображений приложения. */
|
||||
/**
|
||||
* Отдельная страница «Галерея»: просмотр/загрузка/удаление всех изображений приложения.
|
||||
*
|
||||
* Страница занимает экран целиком и сама не прокручивается: списка на сотни картинок здесь нет
|
||||
* пагинации, и две полосы прокрутки — внешняя и внутри сетки — мешали бы друг другу.
|
||||
*/
|
||||
export function GalleryPanel() {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex h-[calc(100vh-13rem)] min-h-96 flex-col gap-4">
|
||||
<h2 className="crt-glow text-xl font-semibold">{t('admin.gallery.title')}</h2>
|
||||
<div className="crt-panel rounded-md p-4">
|
||||
<GalleryBrowser />
|
||||
<div className="crt-panel flex min-h-0 flex-1 flex-col rounded-md p-4">
|
||||
<GalleryBrowser fill />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useApiError } from '@/shared/lib/use-api-error'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/shared/ui/dialog'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { cn } from '@/shared/lib/cn'
|
||||
import { deleteImage, imageUrl, listImages, uploadImage } from './api'
|
||||
|
||||
const CATEGORIES: ImageCategory[] = ['Library', 'ShowPoster', 'EpisodeStill', 'BumperBackground']
|
||||
@@ -25,10 +26,13 @@ export function GalleryBrowser({
|
||||
category = 'Library',
|
||||
onSelect,
|
||||
onClose,
|
||||
fill = false,
|
||||
}: Readonly<{
|
||||
category?: ImageCategory
|
||||
onSelect?: (image: ImagePick) => void
|
||||
onClose?: () => void
|
||||
/** Занять экран целиком: на отдельной странице прокрутка должна быть одна — внутри сетки. */
|
||||
fill?: boolean
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -86,7 +90,7 @@ export function GalleryBrowser({
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn('flex flex-col', fill && 'min-h-0 flex-1')}>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{CATEGORIES.map((c) => (
|
||||
<button
|
||||
@@ -109,7 +113,7 @@ export function GalleryBrowser({
|
||||
{onSelect ? t('admin.gallery.pickHint') : t('admin.gallery.browseHint')}
|
||||
</span>
|
||||
<Select value={order} onValueChange={(v) => setOrder(v as ImageOrder)}>
|
||||
<SelectTrigger className="ml-auto h-8 w-40">
|
||||
<SelectTrigger className="ml-auto h-8 w-48">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -141,7 +145,14 @@ export function GalleryBrowser({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 max-h-[55vh] overflow-y-auto">
|
||||
<div
|
||||
className={cn(
|
||||
'mt-3 overflow-y-auto',
|
||||
// В диалоге высота ограничена долей экрана, на своей странице — остатком до низа окна:
|
||||
// иначе страница прокручивается вместе с сеткой и полос становится две.
|
||||
fill ? 'min-h-0 flex-1' : 'max-h-[55vh]',
|
||||
)}
|
||||
>
|
||||
{isLoading && (
|
||||
<p className="py-6 text-center text-sm text-muted-foreground">{t('common.loading')}</p>
|
||||
)}
|
||||
@@ -183,7 +194,7 @@ export function GalleryBrowser({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ export function ShowPicker({
|
||||
const [query, setQuery] = useState('')
|
||||
const [active, setActive] = useState(0)
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
const searchRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const trimmed = query.trim()
|
||||
const matches = useMemo(() => {
|
||||
@@ -133,9 +134,15 @@ export function ShowPicker({
|
||||
<PopoverContent
|
||||
className="w-[var(--radix-popover-trigger-width)] min-w-64 p-1"
|
||||
onKeyDown={onKeyDown}
|
||||
onOpenAutoFocus={(event) => {
|
||||
// Фокус наводим сами: внутри диалога за него борются две фокус-ловушки, и по умолчанию
|
||||
// он оседает на самом поповере — строка поиска остаётся мёртвой.
|
||||
event.preventDefault()
|
||||
requestAnimationFrame(() => searchRef.current?.focus())
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
autoFocus
|
||||
ref={searchRef}
|
||||
className="h-8"
|
||||
placeholder={t('admin.shows.pickerSearch')}
|
||||
value={query}
|
||||
|
||||
Reference in New Issue
Block a user