186 lines
8.1 KiB
JavaScript
186 lines
8.1 KiB
JavaScript
/**
|
||
* @file SettingsPage.jsx
|
||
* @description Page for user settings: font size controls for all UI categories.
|
||
* Settings persist to localStorage via FontSettingsContext.
|
||
*/
|
||
import React from "react";
|
||
import { useNavigate } from "react-router-dom";
|
||
import { useFontSettings, FONT_CATEGORIES } from "../context/FontSettingsContext";
|
||
import { Panel } from "../components/UI/Panel";
|
||
import { Button } from "../components/UI/Button";
|
||
|
||
const PRESETS = [
|
||
{ label: "Мелкий", scales: { table: 0.85, card: 0.85, nav: 0.85, heading: 0.85, body: 0.85, small: 0.85 } },
|
||
{ label: "Стандарт", scales: { table: 1.0, card: 1.0, nav: 1.0, heading: 1.0, body: 1.0, small: 1.0 } },
|
||
{ label: "Крупный", scales: { table: 1.15, card: 1.15, nav: 1.15, heading: 1.2, body: 1.15, small: 1.1 } },
|
||
{ label: "Очень крупный", scales: { table: 1.3, card: 1.3, nav: 1.25, heading: 1.4, body: 1.3, small: 1.2 } },
|
||
];
|
||
|
||
const Slider = ({ category, value, onChange }) => {
|
||
const pct = ((value - category.min) / (category.max - category.min)) * 100;
|
||
|
||
return (
|
||
<div className="space-y-2">
|
||
<div className="flex items-baseline justify-between">
|
||
<div>
|
||
<span className="font-medium text-[var(--color-text)]">{category.label}</span>
|
||
<p className="text-xs text-[var(--color-text-muted)]">{category.description}</p>
|
||
</div>
|
||
<span className="text-sm font-semibold tabular-nums text-[var(--color-accent)]">
|
||
{Math.round(value * 100)}%
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<input
|
||
type="range"
|
||
min={category.min}
|
||
max={category.max}
|
||
step={category.step}
|
||
value={value}
|
||
onChange={(e) => onChange(parseFloat(e.target.value))}
|
||
className="fs-slider flex-1"
|
||
style={{
|
||
background: `linear-gradient(to right, var(--color-accent) 0%, var(--color-accent) ${pct}%, var(--color-border) ${pct}%, var(--color-border) 100%)`,
|
||
}}
|
||
aria-label={category.label}
|
||
/>
|
||
<span className="text-xs tabular-nums text-[var(--color-text-muted)] w-16 text-right">
|
||
{value.toFixed(2)}×
|
||
</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const SettingsPage = () => {
|
||
const { settings, updateCategory, resetAll, categories } = useFontSettings();
|
||
const navigate = useNavigate();
|
||
|
||
const applyPreset = (scales) => {
|
||
for (const [key, scale] of Object.entries(scales)) {
|
||
updateCategory(key, scale);
|
||
}
|
||
};
|
||
|
||
// Check if current settings match a preset
|
||
const activePreset = PRESETS.findIndex((p) =>
|
||
Object.entries(p.scales).every(([k, v]) => Math.abs((settings[k] ?? 1.0) - v) < 0.001)
|
||
);
|
||
|
||
return (
|
||
<div className="mx-auto max-w-3xl space-y-6 px-3 py-4 sm:px-4 md:px-6 md:py-8">
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-xs uppercase tracking-[0.2em] text-[var(--color-text-muted)]">Настройки</p>
|
||
<h1 className="mt-1 text-2xl font-bold leading-tight" style={{ fontSize: `calc(1.5rem * var(--fs-scale-heading, 1))` }}>
|
||
Настройки интерфейса
|
||
</h1>
|
||
</div>
|
||
<Button variant="ghost" onClick={() => navigate(-1)}>
|
||
← Назад
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Presets */}
|
||
<Panel className="p-5 space-y-3">
|
||
<h2 className="text-sm font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||
Быстрые пресеты
|
||
</h2>
|
||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||
{PRESETS.map((preset, i) => (
|
||
<button
|
||
key={preset.label}
|
||
type="button"
|
||
onClick={() => applyPreset(preset.scales)}
|
||
className={[
|
||
"rounded-[18px] border-2 px-4 py-3 text-center transition",
|
||
activePreset === i
|
||
? "border-[var(--color-accent)] bg-[var(--color-accent-soft)] text-[var(--color-text)] font-semibold"
|
||
: "border-[var(--color-border)] bg-[var(--color-surface-strong)] text-[var(--color-text-muted)] hover:border-[var(--color-accent)] hover:text-[var(--color-text)]",
|
||
].join(" ")}
|
||
>
|
||
<div className="text-sm font-medium">{preset.label}</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</Panel>
|
||
|
||
{/* Font size sliders */}
|
||
<Panel className="p-5 space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<h2 className="text-sm font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||
Размеры шрифтов
|
||
</h2>
|
||
<Button size="sm" variant="ghost" onClick={resetAll}>
|
||
Сбросить
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="space-y-5">
|
||
{categories.map((cat) => (
|
||
<Slider
|
||
key={cat.key}
|
||
category={cat}
|
||
value={settings[cat.key] ?? cat.defaultScale}
|
||
onChange={(v) => updateCategory(cat.key, v)}
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
{/* Preview */}
|
||
<div className="rounded-[18px] border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4 space-y-2">
|
||
<p className="text-xs uppercase tracking-wider text-[var(--color-text-muted)]">Предпросмотр</p>
|
||
<div className="fs-zone-heading space-y-1">
|
||
<h3 className="font-bold" style={{ fontSize: `calc(1.25rem * var(--fs-scale-heading, 1))` }}>
|
||
Заголовок секции
|
||
</h3>
|
||
</div>
|
||
<div className="fs-zone-nav flex gap-2">
|
||
<span className="rounded-[14px] bg-[var(--color-accent)] px-3 py-1.5 text-[var(--color-accent-contrast)]"
|
||
style={{ fontSize: `calc(0.875rem * var(--fs-scale-nav, 1))` }}>
|
||
Пункт меню
|
||
</span>
|
||
<span className="rounded-[14px] bg-[var(--color-surface)] px-3 py-1.5 border border-[var(--color-border)]"
|
||
style={{ fontSize: `calc(0.875rem * var(--fs-scale-nav, 1))` }}>
|
||
Вкладка
|
||
</span>
|
||
</div>
|
||
<div className="fs-zone-table rounded-[14px] border border-[var(--color-border)] overflow-hidden">
|
||
<table className="w-full">
|
||
<thead>
|
||
<tr className="bg-[var(--color-surface)]">
|
||
<th className="px-3 py-2 text-left font-semibold" style={{ fontSize: `calc(0.875rem * var(--fs-scale-table, 1))` }}>Дата</th>
|
||
<th className="px-3 py-2 text-left font-semibold" style={{ fontSize: `calc(0.875rem * var(--fs-scale-table, 1))` }}>Статус</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr className="border-t border-[var(--color-border)]">
|
||
<td className="px-3 py-2" style={{ fontSize: `calc(0.875rem * var(--fs-scale-table, 1))` }}>02.07.2026</td>
|
||
<td className="px-3 py-2" style={{ fontSize: `calc(0.875rem * var(--fs-scale-table, 1))` }}>В работе</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div className="fs-zone-card rounded-[14px] bg-[var(--color-surface)] p-3">
|
||
<p style={{ fontSize: `calc(0.875rem * var(--fs-scale-card, 1))` }}>
|
||
Текст в карточке — описание доставки или заказа.
|
||
</p>
|
||
</div>
|
||
<p className="fs-zone-body text-[var(--color-text)]" style={{ fontSize: `calc(1rem * var(--fs-scale-body, 1))` }}>
|
||
Основной текст интерфейса.
|
||
</p>
|
||
<p className="fs-zone-small text-[var(--color-text-muted)]" style={{ fontSize: `calc(0.75rem * var(--fs-scale-small, 1))` }}>
|
||
мелкая подпись · временная отметка
|
||
</p>
|
||
</div>
|
||
</Panel>
|
||
|
||
<p className="text-center text-xs text-[var(--color-text-muted)]">
|
||
Настройки сохраняются на этом устройстве
|
||
</p>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default SettingsPage; |