typescript
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import React, {
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import PanelHeader from '../ui/PanelHeader';
|
||||
import { DEFAULT_SETTINGS_SECTIONS } from './sections';
|
||||
|
||||
export interface SettingsSectionConfig {
|
||||
id: string;
|
||||
label: string;
|
||||
component?: React.ComponentType<any>;
|
||||
render?: (props: Record<string, unknown>) => ReactNode;
|
||||
}
|
||||
|
||||
interface SettingsModalProps {
|
||||
open?: boolean;
|
||||
onClose?: () => void;
|
||||
sections?: SettingsSectionConfig[];
|
||||
defaultSectionId?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const SettingsModal: React.FC<SettingsModalProps> = ({
|
||||
open = false,
|
||||
onClose,
|
||||
sections,
|
||||
defaultSectionId,
|
||||
...sectionProps
|
||||
}) => {
|
||||
const sectionList: SettingsSectionConfig[] = useMemo(() => {
|
||||
if (Array.isArray(sections) && sections.length) {
|
||||
return sections;
|
||||
}
|
||||
return DEFAULT_SETTINGS_SECTIONS as SettingsSectionConfig[];
|
||||
}, [sections]);
|
||||
|
||||
const firstSectionId = sectionList[0]?.id ?? null;
|
||||
const resolvedDefaultSection = defaultSectionId || firstSectionId;
|
||||
|
||||
const [activeSection, setActiveSection] = useState(resolvedDefaultSection);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setActiveSection(resolvedDefaultSection);
|
||||
return;
|
||||
}
|
||||
const hasActiveSection = sectionList.some((section) => section.id === activeSection);
|
||||
if (!hasActiveSection) {
|
||||
setActiveSection(resolvedDefaultSection);
|
||||
}
|
||||
}, [open, sectionList, resolvedDefaultSection, activeSection]);
|
||||
|
||||
const handleBackdropClick = useCallback(() => {
|
||||
onClose?.();
|
||||
}, [onClose]);
|
||||
|
||||
const handleInnerClick = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeSectionConfig = sectionList.find((section) => section.id === activeSection);
|
||||
let sectionContent = null;
|
||||
if (activeSectionConfig) {
|
||||
if (activeSectionConfig.component) {
|
||||
const SectionComponent = activeSectionConfig.component;
|
||||
sectionContent = <SectionComponent {...sectionProps} />;
|
||||
} else if (typeof activeSectionConfig.render === 'function') {
|
||||
sectionContent = activeSectionConfig.render(sectionProps);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" role="presentation" onClick={handleBackdropClick}>
|
||||
<div
|
||||
className="modal modal--panel settings-modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="settings-modal-title"
|
||||
onClick={handleInnerClick}
|
||||
>
|
||||
<PanelHeader
|
||||
className="panel-modal__header"
|
||||
title="Settings"
|
||||
titleTag="h3"
|
||||
titleProps={{ id: 'settings-modal-title' }}
|
||||
actions={(
|
||||
<button type="button" className="secondary" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<div className="settings-modal__body">
|
||||
<nav className="settings-modal__sidebar" aria-label="Settings sections">
|
||||
<ul>
|
||||
{sectionList.map((section) => (
|
||||
<li key={section.id}>
|
||||
<button
|
||||
type="button"
|
||||
className={section.id === activeSection ? 'active' : ''}
|
||||
onClick={() => setActiveSection(section.id)}
|
||||
>
|
||||
{section.label}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="settings-modal__content">
|
||||
{sectionContent || (
|
||||
<p>Select a settings section.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsModal;
|
||||
Reference in New Issue
Block a user