Add tab bar position setting with edge swipe sidebar

- Add theme_tab_bar_position setting (top/bottom/responsive)
- Tab bar is now fixed at top, stays visible during scroll
- Bottom position uses fixed positioning with safe-area-inset
- Add edge swipe gesture to open sidebar on mobile
- Remove backdrop-filter for better scroll performance
- Simplify TabsScroller by removing inline style manipulation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-23 02:05:49 +01:00
parent f2c6389b21
commit 02c14e3fbd
9 changed files with 428 additions and 26 deletions

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { useTheme, COLOR_PALETTES } from '../../contexts/ThemeContext';
import type { AccentColor, BorderRadius, SidebarStyle, Density, FontFamily, ColorPalette } from '../../contexts/ThemeContext';
import type { AccentColor, BorderRadius, SidebarStyle, Density, FontFamily, ColorPalette, TabBarPosition } from '../../contexts/ThemeContext';
import { useTranslation } from '../../contexts/LanguageContext';
import { useAuth } from '../../contexts/AuthContext';
import { useSidebar } from '../../contexts/SidebarContext';
@@ -39,6 +39,8 @@ export default function ThemeSettings() {
setColorPalette,
customColors,
setCustomColors,
tabBarPosition,
setTabBarPosition,
saveThemeToBackend
} = useTheme();
const { t } = useTranslation();
@@ -96,6 +98,11 @@ export default function ThemeSettings() {
saveThemeToBackend({ colorPalette: palette }).catch(console.error);
};
const handleTabBarPositionChange = async (position: TabBarPosition) => {
setTabBarPosition(position);
saveThemeToBackend({ tabBarPosition: position }).catch(console.error);
};
// Color picker popup state
const [colorPickerState, setColorPickerState] = useState<ColorPickerState>(null);
const hasUserModifiedCustomColors = useRef(false);
@@ -161,6 +168,12 @@ export default function ThemeSettings() {
{ id: 'roboto', label: t.theme.fontOptions.roboto, description: t.theme.fontOptions.robotoDesc, fontStyle: 'Roboto' },
];
const tabBarPositions: { id: TabBarPosition; label: string; description: string }[] = [
{ id: 'top', label: t.theme.tabBarPositions?.top || 'Sopra', description: t.theme.tabBarPositions?.topDesc || 'Barra dei tab sempre in alto' },
{ id: 'bottom', label: t.theme.tabBarPositions?.bottom || 'Sotto', description: t.theme.tabBarPositions?.bottomDesc || 'Barra dei tab sempre in basso' },
{ id: 'responsive', label: t.theme.tabBarPositions?.responsive || 'Responsivo', description: t.theme.tabBarPositions?.responsiveDesc || 'In alto su desktop, in basso su mobile' },
];
const palettes: { id: ColorPalette; label: string; description: string }[] = [
{ id: 'monochrome', label: t.theme.palettes.monochrome, description: t.theme.palettes.monochromeDesc },
{ id: 'default', label: t.theme.palettes.default, description: t.theme.palettes.defaultDesc },
@@ -471,6 +484,40 @@ export default function ThemeSettings() {
))}
</div>
</div>
<div className="theme-section">
<div className="section-header">
<h3 className="section-title">{t.theme.tabBarPosition || 'Posizione Tab'}</h3>
</div>
<div className="option-cards">
{tabBarPositions.map((pos) => (
<div
key={pos.id}
role="button"
tabIndex={0}
className={`option-card ${tabBarPosition === pos.id ? 'active' : ''}`}
onClick={() => handleTabBarPositionChange(pos.id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleTabBarPositionChange(pos.id);
}
}}
>
<div className="option-preview">
<div className={`tabbar-preview tabbar-preview-${pos.id}`}>
<div className="tabbar-line"></div>
<div className="tabbar-content"></div>
</div>
</div>
<div className="option-info">
<span className="option-name">{pos.label}</span>
<span className="option-description">{pos.description}</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);