fix: mobile/responsive issues in FAQ and info pages

- FAQ builder: touch targets 36px → 44px (move up/down/delete buttons)
- Filter tabs: add flex-wrap to prevent overflow on narrow screens
- Accordion: add ResizeObserver for dynamic content height (images
  loading, viewport rotation won't clip content anymore)
- Accordion: use stable key (index+question) instead of filtered array
  index — search+toggle no longer opens wrong item
This commit is contained in:
Fringg
2026-04-24 14:10:28 +03:00
parent 1ee0f18343
commit c16593aeee
3 changed files with 30 additions and 20 deletions

View File

@@ -325,7 +325,7 @@ function FaqBuilder({ items, onChange, locale, localeLabel }: FaqBuilderProps) {
type="button" type="button"
onClick={() => handleMoveUp(index)} onClick={() => handleMoveUp(index)}
disabled={index === 0} disabled={index === 0}
className="min-h-[36px] min-w-[36px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200 disabled:cursor-not-allowed disabled:opacity-30" className="min-h-[44px] min-w-[44px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200 disabled:cursor-not-allowed disabled:opacity-30"
title={t('admin.infoPages.faq.moveUp')} title={t('admin.infoPages.faq.moveUp')}
> >
<ChevronUpIcon /> <ChevronUpIcon />
@@ -334,7 +334,7 @@ function FaqBuilder({ items, onChange, locale, localeLabel }: FaqBuilderProps) {
type="button" type="button"
onClick={() => handleMoveDown(index)} onClick={() => handleMoveDown(index)}
disabled={index >= items.length - 1} disabled={index >= items.length - 1}
className="min-h-[36px] min-w-[36px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200 disabled:cursor-not-allowed disabled:opacity-30" className="min-h-[44px] min-w-[44px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200 disabled:cursor-not-allowed disabled:opacity-30"
title={t('admin.infoPages.faq.moveDown')} title={t('admin.infoPages.faq.moveDown')}
> >
<ChevronDownIcon /> <ChevronDownIcon />
@@ -342,7 +342,7 @@ function FaqBuilder({ items, onChange, locale, localeLabel }: FaqBuilderProps) {
<button <button
type="button" type="button"
onClick={() => handleRemove(index)} onClick={() => handleRemove(index)}
className="min-h-[36px] min-w-[36px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-error-500/10 hover:text-error-400" className="min-h-[44px] min-w-[44px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-error-500/10 hover:text-error-400"
title={t('admin.infoPages.faq.removeQuestion')} title={t('admin.infoPages.faq.removeQuestion')}
> >
<TrashSmallIcon /> <TrashSmallIcon />

View File

@@ -324,7 +324,7 @@ export default function AdminInfoPages() {
</div> </div>
{/* Filter tabs */} {/* Filter tabs */}
<div className="flex gap-1"> <div className="flex flex-wrap gap-1">
{(['all', 'page', 'faq'] as const).map((tab) => ( {(['all', 'page', 'faq'] as const).map((tab) => (
<button <button
key={tab} key={tab}

View File

@@ -216,6 +216,16 @@ function FaqAccordionItem({
} }
}, [isOpen]); }, [isOpen]);
// Update height when content resizes (images loading, viewport rotation)
useEffect(() => {
if (!isOpen || !contentRef.current) return;
const observer = new ResizeObserver(() => {
if (contentRef.current) setHeight(contentRef.current.scrollHeight);
});
observer.observe(contentRef.current);
return () => observer.disconnect();
}, [isOpen]);
const sanitizedAnswer = useMemo(() => sanitizeHtml(item.a), [item.a]); const sanitizedAnswer = useMemo(() => sanitizeHtml(item.a), [item.a]);
return ( return (
@@ -246,15 +256,12 @@ function FaqAccordionItem({
function FaqView({ items }: { items: FaqItem[] }) { function FaqView({ items }: { items: FaqItem[] }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [openIndex, setOpenIndex] = useState<number | null>(null); const [openKey, setOpenKey] = useState<string | null>(null);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const handleToggle = useCallback( const handleToggle = useCallback((key: string) => {
(index: number) => { setOpenKey((prev) => (prev === key ? null : key));
setOpenIndex(openIndex === index ? null : index); }, []);
},
[openIndex],
);
const filteredItems = useMemo(() => { const filteredItems = useMemo(() => {
if (!search.trim()) return items; if (!search.trim()) return items;
@@ -275,7 +282,7 @@ function FaqView({ items }: { items: FaqItem[] }) {
value={search} value={search}
onChange={(e) => { onChange={(e) => {
setSearch(e.target.value); setSearch(e.target.value);
setOpenIndex(null); setOpenKey(null);
}} }}
placeholder={t('admin.infoPages.faq.searchPlaceholder')} placeholder={t('admin.infoPages.faq.searchPlaceholder')}
className="input pl-9 text-sm" className="input pl-9 text-sm"
@@ -290,14 +297,17 @@ function FaqView({ items }: { items: FaqItem[] }) {
</div> </div>
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
{filteredItems.map((item, index) => ( {filteredItems.map((item, index) => {
const key = `${index}-${item.q.slice(0, 50)}`;
return (
<FaqAccordionItem <FaqAccordionItem
key={index} key={key}
item={item} item={item}
isOpen={openIndex === index} isOpen={openKey === key}
onToggle={() => handleToggle(index)} onToggle={() => handleToggle(key)}
/> />
))} );
})}
</div> </div>
)} )}
</div> </div>