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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user