refactor(web): split SettingsView into settings-view module
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
81a92b9b5a
commit
1a1b4972af
10 changed files with 272 additions and 199 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { SettingsView } from './SettingsView';
|
||||
import { SettingsView, SettingsViewSkeleton } from './settings-view';
|
||||
|
||||
/**
|
||||
* SettingsView - Vue principale des paramètres
|
||||
|
|
@ -8,42 +8,55 @@ import { SettingsView } from './SettingsView';
|
|||
* sections de paramètres (profil, compte, apparence, sécurité, etc.)
|
||||
*/
|
||||
const meta: Meta<typeof SettingsView> = {
|
||||
title: 'Components/Features/Views/SettingsView',
|
||||
component: SettingsView,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Vue avec navigation par onglets vers toutes les sections de paramètres.',
|
||||
},
|
||||
},
|
||||
title: 'Components/Features/Views/SettingsView',
|
||||
component: SettingsView,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
'Vue avec navigation par onglets vers toutes les sections de paramètres.',
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
initialTab: {
|
||||
control: 'select',
|
||||
options: ['profile', 'account', 'appearance', 'accessibility', 'security', 'integrations', 'cloud', 'backups', 'data', 'audio', 'notifications'],
|
||||
description: 'Onglet à afficher par défaut',
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
initialTab: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'profile',
|
||||
'account',
|
||||
'appearance',
|
||||
'accessibility',
|
||||
'security',
|
||||
'integrations',
|
||||
'cloud',
|
||||
'backups',
|
||||
'data',
|
||||
'audio',
|
||||
'notifications',
|
||||
],
|
||||
description: 'Onglet à afficher par défaut',
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="bg-kodo-background min-h-screen p-4">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="bg-kodo-background min-h-layout-page p-4">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* État par défaut avec onglet Profile.
|
||||
*/
|
||||
export const Default: Story = {
|
||||
name: 'Par défaut (Profile)',
|
||||
args: {
|
||||
initialTab: 'profile',
|
||||
},
|
||||
export const Loading: Story = {
|
||||
name: 'Loading',
|
||||
render: () => <SettingsViewSkeleton />,
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
name: 'Par défaut (Profile)',
|
||||
args: { initialTab: 'profile' },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,167 +1 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Card } from '../ui/card';
|
||||
import { Tabs, TabsList, TabsTrigger } from '../ui/tabs';
|
||||
|
||||
import { useToast } from '../../components/feedback/ToastProvider';
|
||||
import {
|
||||
User,
|
||||
Bell,
|
||||
Palette,
|
||||
Shield,
|
||||
Volume2,
|
||||
UserCog,
|
||||
Cloud,
|
||||
Database,
|
||||
Accessibility,
|
||||
Plug,
|
||||
HardDrive,
|
||||
} from 'lucide-react';
|
||||
import { SecuritySettings } from '../settings/security/SecuritySettings';
|
||||
import { EditProfile } from '../settings/profile/EditProfile';
|
||||
import { AccountSettings } from '../settings/account/AccountSettings';
|
||||
import { CloudIntegrationView } from '../settings/cloud/CloudIntegrationView';
|
||||
import { BackupsView } from '../settings/backups/BackupsView';
|
||||
import { AppearanceSettingsView } from '../settings/appearance/AppearanceSettingsView';
|
||||
import { AccessibilitySettingsView } from '../settings/accessibility/AccessibilitySettingsView';
|
||||
import { IntegrationsView } from '../settings/integrations/IntegrationsView';
|
||||
import { DataExportView } from '../settings/data/DataExportView';
|
||||
|
||||
interface SettingsViewProps {
|
||||
initialTab?: string;
|
||||
}
|
||||
|
||||
export const SettingsView: React.FC<SettingsViewProps> = ({
|
||||
initialTab = 'profile',
|
||||
}) => {
|
||||
const { addToast: _addToast } = useToast();
|
||||
const [activeTab, setActiveTab] = useState(initialTab);
|
||||
|
||||
// Sync active tab if initialTab changes
|
||||
useEffect(() => {
|
||||
if (initialTab) setActiveTab(initialTab);
|
||||
}, [initialTab]);
|
||||
|
||||
const settingsTabs = [
|
||||
{ id: 'profile', label: 'Profile', icon: <User className="w-4 h-4" /> },
|
||||
{ id: 'account', label: 'Account', icon: <UserCog className="w-4 h-4" /> },
|
||||
{
|
||||
id: 'appearance',
|
||||
label: 'Appearance',
|
||||
icon: <Palette className="w-4 h-4" />,
|
||||
},
|
||||
{
|
||||
id: 'accessibility',
|
||||
label: 'Accessibility',
|
||||
icon: <Accessibility className="w-4 h-4" />,
|
||||
},
|
||||
{ id: 'security', label: 'Security', icon: <Shield className="w-4 h-4" /> },
|
||||
{
|
||||
id: 'integrations',
|
||||
label: 'Integrations',
|
||||
icon: <Plug className="w-4 h-4" />,
|
||||
},
|
||||
{ id: 'cloud', label: 'Cloud & Sync', icon: <Cloud className="w-4 h-4" /> },
|
||||
{
|
||||
id: 'backups',
|
||||
label: 'Backups',
|
||||
icon: <HardDrive className="w-4 h-4" />,
|
||||
},
|
||||
{
|
||||
id: 'data',
|
||||
label: 'Privacy & Data',
|
||||
icon: <Database className="w-4 h-4" />,
|
||||
},
|
||||
{ id: 'audio', label: 'Audio', icon: <Volume2 className="w-4 h-4" /> },
|
||||
{
|
||||
id: 'notifications',
|
||||
label: 'Notifications',
|
||||
icon: <Bell className="w-4 h-4" />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8 animate-fadeIn pb-20">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h2 className="text-3xl font-display font-bold text-white mb-2">
|
||||
SETTINGS
|
||||
</h2>
|
||||
<p className="text-kodo-content-dim font-mono text-sm">
|
||||
Configure your studio, account, and preferences.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Top Navigation using new Tabs component */}
|
||||
<div className="border-b border-kodo-steel/50">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="pb-0">
|
||||
<TabsList className="bg-transparent border-none p-0">
|
||||
{settingsTabs.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
className="rounded-none border-b-2 border-transparent data-[state=active]:border-kodo-steel data-[state=active]:bg-transparent data-[state=active]:shadow-none px-4 py-2"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{tab.icon}
|
||||
{tab.label}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="min-h-[500px]">
|
||||
{/* PROFILE TAB (Phase 3) */}
|
||||
{activeTab === 'profile' && <EditProfile />}
|
||||
|
||||
{/* ACCOUNT TAB (Phase 4) */}
|
||||
{activeTab === 'account' && <AccountSettings />}
|
||||
|
||||
{/* APPEARANCE TAB (Phase 21) */}
|
||||
{activeTab === 'appearance' && <AppearanceSettingsView />}
|
||||
|
||||
{/* ACCESSIBILITY TAB (Phase 21) */}
|
||||
{activeTab === 'accessibility' && <AccessibilitySettingsView />}
|
||||
|
||||
{/* SECURITY TAB (Phase 2) */}
|
||||
{activeTab === 'security' && <SecuritySettings />}
|
||||
|
||||
{/* INTEGRATIONS TAB (Phase 23) */}
|
||||
{activeTab === 'integrations' && <IntegrationsView />}
|
||||
|
||||
{/* CLOUD TAB (Phase 18) */}
|
||||
{activeTab === 'cloud' && <CloudIntegrationView />}
|
||||
|
||||
{/* BACKUPS TAB (Phase 18) */}
|
||||
{activeTab === 'backups' && <BackupsView />}
|
||||
|
||||
{/* DATA EXPORT TAB (Phase 23) */}
|
||||
{activeTab === 'data' && <DataExportView />}
|
||||
|
||||
{/* Placeholder for Audio & Notifications */}
|
||||
{['audio', 'notifications'].includes(activeTab) && (
|
||||
<Card variant="default" className="min-h-[400px]">
|
||||
<div className="flex flex-col items-center justify-center h-full text-center animate-fadeIn py-24">
|
||||
<div className="w-20 h-20 rounded-full bg-kodo-slate flex items-center justify-center mb-6">
|
||||
{activeTab === 'audio' ? (
|
||||
<Volume2 className="w-10 h-10 text-kodo-content-dim" />
|
||||
) : (
|
||||
<Bell className="w-10 h-10 text-kodo-content-dim" />
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white capitalize mb-2">
|
||||
{activeTab} Settings
|
||||
</h3>
|
||||
<p className="text-kodo-content-dim max-w-md">
|
||||
Advanced configurations for {activeTab} will be available in the
|
||||
next system update (v2.1).
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export { SettingsView } from './settings-view';
|
||||
|
|
|
|||
29
apps/web/src/components/views/settings-view/SettingsView.tsx
Normal file
29
apps/web/src/components/views/settings-view/SettingsView.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import { useSettingsView } from './useSettingsView';
|
||||
import { SettingsViewHeader } from './SettingsViewHeader';
|
||||
import { SettingsViewTabs } from './SettingsViewTabs';
|
||||
import { SettingsViewContent } from './SettingsViewContent';
|
||||
import type { SettingsViewProps } from './types';
|
||||
import type { SettingsTabId } from './types';
|
||||
|
||||
export function SettingsView({ initialTab = 'profile' }: SettingsViewProps = {}) {
|
||||
const { activeTab, setActiveTab, tabs } = useSettingsView(
|
||||
(initialTab as SettingsTabId) ?? 'profile',
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8 animate-fadeIn pb-20">
|
||||
<SettingsViewHeader />
|
||||
|
||||
<SettingsViewTabs
|
||||
activeTab={activeTab}
|
||||
onTabChange={(v) => setActiveTab(v as SettingsTabId)}
|
||||
tabs={tabs}
|
||||
/>
|
||||
|
||||
<div className="min-h-layout-page">
|
||||
<SettingsViewContent activeTab={activeTab} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Volume2, Bell } from 'lucide-react';
|
||||
import { SecuritySettings } from '@/components/settings/security/SecuritySettings';
|
||||
import { EditProfile } from '@/components/settings/profile/EditProfile';
|
||||
import { AccountSettings } from '@/components/settings/account/AccountSettings';
|
||||
import { CloudIntegrationView } from '@/components/settings/cloud/CloudIntegrationView';
|
||||
import { BackupsView } from '@/components/settings/backups/BackupsView';
|
||||
import { AppearanceSettingsView } from '@/components/settings/appearance/AppearanceSettingsView';
|
||||
import { AccessibilitySettingsView } from '@/components/settings/accessibility/AccessibilitySettingsView';
|
||||
import { IntegrationsView } from '@/components/settings/integrations/IntegrationsView';
|
||||
import { DataExportView } from '@/components/settings/data/DataExportView';
|
||||
import type { SettingsTabId } from './types';
|
||||
|
||||
interface SettingsViewContentProps {
|
||||
activeTab: SettingsTabId;
|
||||
}
|
||||
|
||||
export function SettingsViewContent({ activeTab }: SettingsViewContentProps) {
|
||||
if (activeTab === 'profile') return <EditProfile />;
|
||||
if (activeTab === 'account') return <AccountSettings />;
|
||||
if (activeTab === 'appearance') return <AppearanceSettingsView />;
|
||||
if (activeTab === 'accessibility') return <AccessibilitySettingsView />;
|
||||
if (activeTab === 'security') return <SecuritySettings />;
|
||||
if (activeTab === 'integrations') return <IntegrationsView />;
|
||||
if (activeTab === 'cloud') return <CloudIntegrationView />;
|
||||
if (activeTab === 'backups') return <BackupsView />;
|
||||
if (activeTab === 'data') return <DataExportView />;
|
||||
|
||||
if (activeTab === 'audio' || activeTab === 'notifications') {
|
||||
return (
|
||||
<Card variant="default" className="min-h-layout-page-sm">
|
||||
<div className="flex flex-col items-center justify-center h-full text-center animate-fadeIn py-24">
|
||||
<div className="w-20 h-20 rounded-full bg-kodo-slate flex items-center justify-center mb-6">
|
||||
{activeTab === 'audio' ? (
|
||||
<Volume2 className="w-10 h-10 text-kodo-content-dim" />
|
||||
) : (
|
||||
<Bell className="w-10 h-10 text-kodo-content-dim" />
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white capitalize mb-2">
|
||||
{activeTab} Settings
|
||||
</h3>
|
||||
<p className="text-kodo-content-dim max-w-md">
|
||||
Advanced configurations for {activeTab} will be available in the next
|
||||
system update (v2.1).
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
export function SettingsViewHeader() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-3xl font-display font-bold text-white mb-2">SETTINGS</h2>
|
||||
<p className="text-kodo-content-dim font-mono text-sm">
|
||||
Configure your studio, account, and preferences.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export function SettingsViewSkeleton() {
|
||||
return (
|
||||
<div className="flex flex-col gap-8 animate-fadeIn pb-20">
|
||||
<div>
|
||||
<Skeleton className="h-9 w-48 mb-2" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
|
||||
<div className="border-b border-kodo-steel/50 pb-2">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{[1, 2, 3, 4, 5, 6].map((i) => (
|
||||
<Skeleton key={i} className="h-10 w-24" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-h-layout-page">
|
||||
<Skeleton className="h-96 w-full rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import type { SettingsTabId, SettingsTabConfig } from './types';
|
||||
|
||||
interface SettingsViewTabsProps {
|
||||
activeTab: SettingsTabId;
|
||||
onTabChange: (value: string) => void;
|
||||
tabs: SettingsTabConfig[];
|
||||
}
|
||||
|
||||
export function SettingsViewTabs({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
tabs,
|
||||
}: SettingsViewTabsProps) {
|
||||
return (
|
||||
<div className="border-b border-kodo-steel/50">
|
||||
<Tabs value={activeTab} onValueChange={onTabChange} className="pb-0">
|
||||
<TabsList className="bg-transparent border-none p-0">
|
||||
{tabs.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
className="rounded-none border-b-2 border-transparent data-[state=active]:border-kodo-steel data-[state=active]:bg-transparent data-[state=active]:shadow-none px-4 py-2"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{tab.icon}
|
||||
{tab.label}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
4
apps/web/src/components/views/settings-view/index.ts
Normal file
4
apps/web/src/components/views/settings-view/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export type { SettingsViewProps, SettingsTabId, SettingsTabConfig } from './types';
|
||||
export { SettingsView } from './SettingsView';
|
||||
export { SettingsViewSkeleton } from './SettingsViewSkeleton';
|
||||
export { useSettingsView } from './useSettingsView';
|
||||
23
apps/web/src/components/views/settings-view/types.ts
Normal file
23
apps/web/src/components/views/settings-view/types.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export type SettingsTabId =
|
||||
| 'profile'
|
||||
| 'account'
|
||||
| 'appearance'
|
||||
| 'accessibility'
|
||||
| 'security'
|
||||
| 'integrations'
|
||||
| 'cloud'
|
||||
| 'backups'
|
||||
| 'data'
|
||||
| 'audio'
|
||||
| 'notifications';
|
||||
|
||||
export interface SettingsViewProps {
|
||||
initialTab?: SettingsTabId;
|
||||
}
|
||||
|
||||
export interface SettingsTabConfig {
|
||||
id: SettingsTabId;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
User,
|
||||
Bell,
|
||||
Palette,
|
||||
Shield,
|
||||
Volume2,
|
||||
UserCog,
|
||||
Cloud,
|
||||
Database,
|
||||
Accessibility,
|
||||
Plug,
|
||||
HardDrive,
|
||||
} from 'lucide-react';
|
||||
import type { SettingsTabId, SettingsTabConfig } from './types';
|
||||
|
||||
const SETTINGS_TABS: SettingsTabConfig[] = [
|
||||
{ id: 'profile', label: 'Profile', icon: <User className="w-4 h-4" /> },
|
||||
{ id: 'account', label: 'Account', icon: <UserCog className="w-4 h-4" /> },
|
||||
{ id: 'appearance', label: 'Appearance', icon: <Palette className="w-4 h-4" /> },
|
||||
{ id: 'accessibility', label: 'Accessibility', icon: <Accessibility className="w-4 h-4" /> },
|
||||
{ id: 'security', label: 'Security', icon: <Shield className="w-4 h-4" /> },
|
||||
{ id: 'integrations', label: 'Integrations', icon: <Plug className="w-4 h-4" /> },
|
||||
{ id: 'cloud', label: 'Cloud & Sync', icon: <Cloud className="w-4 h-4" /> },
|
||||
{ id: 'backups', label: 'Backups', icon: <HardDrive className="w-4 h-4" /> },
|
||||
{ id: 'data', label: 'Privacy & Data', icon: <Database className="w-4 h-4" /> },
|
||||
{ id: 'audio', label: 'Audio', icon: <Volume2 className="w-4 h-4" /> },
|
||||
{ id: 'notifications', label: 'Notifications', icon: <Bell className="w-4 h-4" /> },
|
||||
];
|
||||
|
||||
export function useSettingsView(initialTab: SettingsTabId = 'profile') {
|
||||
const [activeTab, setActiveTab] = useState<SettingsTabId>(initialTab);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialTab) setActiveTab(initialTab);
|
||||
}, [initialTab]);
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
tabs: SETTINGS_TABS,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue