Improve mobile swipe tabs with native scroll-snap

- Rewrite SwipeTabs to use CSS scroll-snap for smoother transitions
- Add GPU acceleration hints for fluid animations
- Use native scrolling behavior instead of manual touch handling
- Add swipe-tabs--snap and swipe-tabs--static variants
- Improve height transitions and layout containment
- Update dimension variables for better spacing

🤖 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-21 17:08:50 +01:00
parent fc605f03c9
commit abd8f75efc
10 changed files with 552 additions and 112 deletions

View File

@@ -6,6 +6,7 @@ import { useAuth } from '../../contexts/AuthContext';
import { useSidebar } from '../../contexts/SidebarContext';
import type { SidebarMode } from '../../contexts/SidebarContext';
import { ChromePicker, HuePicker } from 'react-color';
import { SwipeTabs } from '../../components/SwipeTabs';
import '../../styles/ThemeSettings.css';
type ThemeTab = 'colors' | 'appearance' | 'preview' | 'advanced';
@@ -43,6 +44,7 @@ export default function ThemeSettings() {
const { user } = useAuth();
const { toggleMobileMenu, sidebarMode, setSidebarMode } = useSidebar();
const isAdmin = user?.is_superuser || false;
const tabs: ThemeTab[] = isAdmin ? ['colors', 'appearance', 'preview', 'advanced'] : ['colors', 'appearance', 'preview'];
const [tooltip, setTooltip] = useState<{ text: string; left: number; visible: boolean }>({
text: '',
left: 0,
@@ -262,70 +264,10 @@ export default function ThemeSettings() {
setCustomColors({});
};
return (
<main className="main-content theme-settings-root">
{/* Tab Tooltip */}
{tooltip.visible && (
<div
className="admin-tab-tooltip"
style={{ left: tooltip.left }}
>
{tooltip.text}
</div>
)}
{/* Modern Tab Navigation */}
<div className="page-tabs-container">
<div className="page-tabs-slider">
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
<span className="material-symbols-outlined">menu</span>
</button>
<div className="page-title-section">
<span className="page-title-text">{t.theme.title}</span>
</div>
<div className="admin-tabs-divider"></div>
<button
className={`admin-tab-btn ${activeTab === 'colors' ? 'active' : ''}`}
onClick={() => setActiveTab('colors')}
onMouseEnter={(e) => handleTabMouseEnter(t.theme.colorsTab, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">color_lens</span>
<span>{t.theme.colorsTab}</span>
</button>
<button
className={`admin-tab-btn ${activeTab === 'appearance' ? 'active' : ''}`}
onClick={() => setActiveTab('appearance')}
onMouseEnter={(e) => handleTabMouseEnter(t.theme.appearanceTab, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">tune</span>
<span>{t.theme.appearanceTab}</span>
</button>
<button
className={`admin-tab-btn ${activeTab === 'preview' ? 'active' : ''}`}
onClick={() => setActiveTab('preview')}
onMouseEnter={(e) => handleTabMouseEnter(t.theme.previewTab, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">visibility</span>
<span>{t.theme.previewTab}</span>
</button>
{isAdmin && (
<button
className={`admin-tab-btn ${activeTab === 'advanced' ? 'active' : ''}`}
onClick={() => setActiveTab('advanced')}
onMouseEnter={(e) => handleTabMouseEnter(t.theme.advancedTab, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">code</span>
<span>{t.theme.advancedTab}</span>
</button>
)}
</div>
</div>
<div className="admin-tab-content">
{activeTab === 'colors' && (
const renderPanel = (tab: ThemeTab) => {
switch (tab) {
case 'colors':
return (
<div className="theme-tab-content">
<div className="theme-section">
<div className="section-header">
@@ -394,12 +336,11 @@ export default function ThemeSettings() {
</div>
</div>
</div>
)}
{activeTab === 'appearance' && (
);
case 'appearance':
return (
<div className="theme-tab-content">
<div className="appearance-grid">
{/* Border Radius Section */}
<div className="theme-section">
<div className="section-header">
<h3 className="section-title">{t.theme.borderRadius}</h3>
@@ -426,7 +367,6 @@ export default function ThemeSettings() {
</div>
</div>
{/* Sidebar Style Section */}
<div className="theme-section">
<div className="section-header">
<h3 className="section-title">{t.theme.sidebarStyle}</h3>
@@ -453,7 +393,6 @@ export default function ThemeSettings() {
</div>
</div>
{/* Sidebar Mode Section */}
<div className="theme-section">
<div className="section-header">
<h3 className="section-title">{t.admin.sidebarMode}</h3>
@@ -480,7 +419,6 @@ export default function ThemeSettings() {
</div>
</div>
{/* Density Section */}
<div className="theme-section">
<div className="section-header">
<h3 className="section-title">{t.theme.density}</h3>
@@ -508,7 +446,6 @@ export default function ThemeSettings() {
</div>
</div>
{/* Font Family Section */}
<div className="theme-section">
<div className="section-header">
<h3 className="section-title">{t.theme.fontFamily}</h3>
@@ -535,9 +472,9 @@ export default function ThemeSettings() {
</div>
</div>
</div>
)}
{activeTab === 'preview' && (
);
case 'preview':
return (
<div className="theme-tab-content">
<div className="theme-section">
<div className="section-header">
@@ -579,9 +516,10 @@ export default function ThemeSettings() {
</div>
</div>
</div>
)}
{activeTab === 'advanced' && isAdmin && (
);
case 'advanced':
if (!isAdmin) return null;
return (
<div className="theme-tab-content">
<div className="theme-section">
<div className="section-header">
@@ -593,7 +531,6 @@ export default function ThemeSettings() {
</div>
<div className="advanced-colors-grid" style={{ marginTop: '2rem' }}>
{/* Light Theme Colors */}
<div className="color-theme-section">
<h3 className="color-theme-title">{t.theme.lightThemeColors}</h3>
<div className="color-controls-list">
@@ -648,7 +585,6 @@ export default function ThemeSettings() {
</div>
</div>
{/* Dark Theme Colors */}
<div className="color-theme-section">
<h3 className="color-theme-title">{t.theme.darkThemeColors}</h3>
<div className="color-controls-list">
@@ -705,9 +641,83 @@ export default function ThemeSettings() {
</div>
</div>
</div>
)}
);
default:
return null;
}
};
return (
<main className="main-content theme-settings-root">
{/* Tab Tooltip */}
{tooltip.visible && (
<div
className="admin-tab-tooltip"
style={{ left: tooltip.left }}
>
{tooltip.text}
</div>
)}
{/* Modern Tab Navigation */}
<div className="page-tabs-container">
<div className="page-tabs-slider">
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
<span className="material-symbols-outlined">menu</span>
</button>
<div className="page-title-section">
<span className="page-title-text">{t.theme.title}</span>
</div>
<div className="admin-tabs-divider"></div>
<button
className={`admin-tab-btn ${activeTab === 'colors' ? 'active' : ''}`}
onClick={() => setActiveTab('colors')}
onMouseEnter={(e) => handleTabMouseEnter(t.theme.colorsTab, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">color_lens</span>
<span>{t.theme.colorsTab}</span>
</button>
<button
className={`admin-tab-btn ${activeTab === 'appearance' ? 'active' : ''}`}
onClick={() => setActiveTab('appearance')}
onMouseEnter={(e) => handleTabMouseEnter(t.theme.appearanceTab, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">tune</span>
<span>{t.theme.appearanceTab}</span>
</button>
<button
className={`admin-tab-btn ${activeTab === 'preview' ? 'active' : ''}`}
onClick={() => setActiveTab('preview')}
onMouseEnter={(e) => handleTabMouseEnter(t.theme.previewTab, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">visibility</span>
<span>{t.theme.previewTab}</span>
</button>
{isAdmin && (
<button
className={`admin-tab-btn ${activeTab === 'advanced' ? 'active' : ''}`}
onClick={() => setActiveTab('advanced')}
onMouseEnter={(e) => handleTabMouseEnter(t.theme.advancedTab, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">code</span>
<span>{t.theme.advancedTab}</span>
</button>
)}
</div>
</div>
<SwipeTabs
className="admin-tab-swipe"
tabs={tabs}
activeTab={activeTab}
onTabChange={setActiveTab}
renderPanel={renderPanel}
panelClassName="admin-tab-content swipe-panel-content"
/>
{/* Color Picker Popup */}
{colorPickerState && (
<div