Загрузка данных
import { useEffect, useMemo, useState } from 'react';
import { ColorField, NumberField, SelectField, TextAreaField, TextField } from '@src/components/FormFields';
import { DrawingStyleField } from '@src/core/Drawings/types';
import { IndicatorSetting } from '@src/types';
import styles from './index.module.scss';
type SettingsValue = string | number | boolean;
type SettingsValues = Record<string, SettingsValue>;
type SettingsField = IndicatorSetting | DrawingStyleField;
interface SettingsTab {
key: string;
label: string;
fields: SettingsField[];
}
interface EntitySettingsModalProps {
tabs: SettingsTab[];
values: SettingsValues;
onChange: (settings: SettingsValues) => void;
initialTabKey?: string;
}
export const EntitySettingsModal = ({
tabs,
values,
onChange,
initialTabKey,
}: EntitySettingsModalProps) => {
const availableTabs = useMemo(() => tabs.filter((tab) => tab.fields.length > 0), [tabs]);
const [activeTabKey, setActiveTabKey] = useState(initialTabKey ?? availableTabs[0]?.key ?? '');
const [form, setForm] = useState<SettingsValues>(values);
useEffect(() => {
setForm(values);
}, [values]);
useEffect(() => {
if (!availableTabs.find((tab) => tab.key === activeTabKey)) {
setActiveTabKey(availableTabs[0]?.key ?? '');
}
}, [availableTabs, activeTabKey]);
const activeTab = availableTabs.find((tab) => tab.key === activeTabKey) ?? availableTabs[0];
const changeValue = (key: string, value: SettingsValue) => {
setForm((current) => {
const next = {
...current,
[key]: value,
};
onChange(next);
return next;
});
};
const renderField = (field: SettingsField) => {
const value = form[field.key] ?? field.defaultValue;
if (field.type === 'select') {
return (
<SelectField
key={field.key}
label={field.label}
value={String(value)}
options={field.options}
onValueChange={(nextValue) => {
changeValue(field.key, nextValue);
}}
/>
);
}
if (field.type === 'color') {
return (
<ColorField
key={field.key}
label={field.label}
value={String(value)}
onValueChange={(nextValue) => {
changeValue(field.key, nextValue);
}}
/>
);
}
if (field.type === 'text') {
return (
<TextField
key={field.key}
label={field.label}
value={String(value)}
placeholder={field.placeholder}
onValueChange={(nextValue) => {
changeValue(field.key, nextValue);
}}
/>
);
}
if (field.type === 'textarea') {
return (
<TextAreaField
key={field.key}
label={field.label}
value={String(value)}
placeholder={field.placeholder}
onValueChange={(nextValue) => {
changeValue(field.key, nextValue);
}}
/>
);
}
if (field.type === 'boolean') {
return (
<label
key={field.key}
className={styles.checkboxField}
>
<input
type="checkbox"
checked={Boolean(value)}
onChange={(event) => {
changeValue(field.key, event.target.checked);
}}
/>
<span>{field.label}</span>
</label>
);
}
return (
<NumberField
key={field.key}
label={field.label}
value={typeof value === 'number' ? value : field.defaultValue}
min={field.min}
max={field.max}
onValueChange={(nextValue) => {
changeValue(field.key, nextValue);
}}
/>
);
};
return (
<div className={styles.wrapper}>
{availableTabs.length > 1 && (
<div className={styles.tabs}>
{availableTabs.map((tab) => (
<button
key={tab.key}
type="button"
className={tab.key === activeTabKey ? styles.tabActive : styles.tab}
onClick={() => setActiveTabKey(tab.key)}
>
{tab.label}
</button>
))}
</div>
)}
<div className={styles.form}>{activeTab?.fields.map(renderField)}</div>
</div>
);
};
.wrapper {
display: flex;
flex-direction: column;
gap: 16px;
}
.tabs {
display: flex;
gap: 16px;
border-bottom: 1px solid var(--stroke-secondary);
}
.tab,
.tabActive {
padding: 0 0 8px;
background: transparent;
border: none;
color: inherit;
cursor: pointer;
font: inherit;
}
.tabActive {
border-bottom: 2px solid var(--text-accent);
}
.form {
display: flex;
flex-direction: column;
gap: 16px;
}
.checkboxField {
display: flex;
align-items: center;
gap: 8px;
}