Настройки внешнего вида

Сделай экран настроек внешнего вида: сегментированный переключатель темы на три варианта, ползунок масштаба интерфейса от 80 до 140 процентов с показом текущего значения, переключатель плотной вёрстки и раздел «Ещё» под разделителем с выбором шрифта. Справа от заголовка экрана метка с текущей темой. Состояние храни в компоненте и отдавай наружу через колбэк onChange.

appearance-settings__mcp__claude-code__claude-opus-5__1 · 24.09.2026, 13:34:55 мск

Проверки пройдены
MCP · claude-opus-5 · Claude Code
Контекст до задачи~2 405
Токенов на прогон33 009
Цена$0.104
Ошибок компилятора0

Первый вызов модели получил 12 533 токена входа, включая системный промпт Claude Code; вызовов модели 2, каждый отправлял разговор заново, и самый большой получил 18 407 токенов. Контекст до задачи — оценка по длине текста, для русского текста она занижает число токенов в полтора-два раза.

Компилятор

Ошибок нет: все компоненты и пропсы существуют.

Линтер и покрытие

  • inline-styleСтроки 62, 76, 84, 86, 95, 104, 105, 106
    Инлайновые стили вместо классов и токенов библиотеки
Компоненты библиотеки

JxBadge, JxDivider, JxSelect, JxSlider, JxSwitch, JxToggle

Ожидались JxToggle, JxSlider, JxSwitch, JxDivider, JxBadge · покрытие 100%

Вызовы инструментов · 7
  1. get_rules({"task":"ui"}) → ~1247 токенов
  2. get_component_api({"name":"JxToggle"}) → ~368 токенов
  3. get_component_api({"name":"JxSlider"}) → ~354 токенов
  4. get_component_api({"name":"JxSwitch"}) → ~289 токенов
  5. get_component_api({"name":"JxDivider"}) → ~153 токенов
  6. get_component_api({"name":"JxSelect"}) → ~563 токенов
  7. get_component_api({"name":"JxBadge"}) → ~274 токенов
Контекст

Инструменты (search_components,get_component_api,get_component_examples,list_design_tokens,get_docs,get_rules, ~1213)

Вход 4 · выход 2 069 · из кэша 24 546 · в кэш 6 390 · 23 с

Рендер

Код компонента

tsx143 строки
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { useState } from 'react';
import {
  JxBadge,
  JxDivider,
  JxSelect,
  JxSlider,
  JxSwitch,
  JxToggle,
} from '@jinx-ui/react';

type ThemeValue = 'light' | 'dark' | 'system';

export interface AppearanceSettings {
  theme: ThemeValue;
  scale: number;
  compact: boolean;
  font: string;
}

export interface AppearanceSettingsScreenProps {
  value?: AppearanceSettings;
  onChange?: (settings: AppearanceSettings) => void;
}

const THEMES: { value: ThemeValue; label: string }[] = [
  { value: 'light', label: 'Светлая' },
  { value: 'dark', label: 'Тёмная' },
  { value: 'system', label: 'Системная' },
];

const FONTS = [
  { value: 'sans', label: 'Без засечек', meta: 'По умолчанию' },
  { value: 'display', label: 'Акцидентный' },
  { value: 'mono', label: 'Монопространственный' },
];

const DEFAULTS: AppearanceSettings = {
  theme: 'system',
  scale: 100,
  compact: false,
  font: 'sans',
};

export default function AppearanceSettingsScreen({
  value,
  onChange,
}: AppearanceSettingsScreenProps) {
  const [settings, setSettings] = useState<AppearanceSettings>(value ?? DEFAULTS);
  const current = value ?? settings;

  const update = (patch: Partial<AppearanceSettings>) => {
    const next = { ...current, ...patch };
    setSettings(next);
    onChange?.(next);
  };

  const themeLabel =
    THEMES.find((item) => item.value === current.theme)?.label ?? 'Системная';

  return (
    <section
      style={{
        display: 'flex',
        flexDirection: 'column',
        gap: 24,
        maxWidth: 520,
        padding: 24,
        background: 'var(--jx-surface)',
        border: '1px solid var(--jx-border)',
        borderRadius: 'var(--jx-r-lg)',
        boxShadow: 'var(--jx-shadow-sm)',
        color: 'var(--jx-text)',
      }}
    >
      <header
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'space-between',
          gap: 16,
          flexWrap: 'wrap',
        }}
      >
        <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
          <h2
            style={{
              margin: 0,
              fontFamily: 'var(--jx-font-display)',
              fontSize: 20,
              color: 'var(--jx-text)',
            }}
          >
            Внешний вид
          </h2>
          <p style={{ margin: 0, fontSize: 13, color: 'var(--jx-text-2)' }}>
            Настройки применяются сразу и сохраняются для этого устройства.
          </p>
        </div>
        <JxBadge tone="accent" dot>
          {themeLabel}
        </JxBadge>
      </header>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          <span style={{ fontSize: 13, color: 'var(--jx-text-2)' }}>Тема</span>
          <JxToggle
            items={THEMES}
            value={current.theme}
            onValueChange={(next) => update({ theme: next as ThemeValue })}
          />
        </div>

        <JxSlider
          label="Масштаб интерфейса"
          min={80}
          max={140}
          step={10}
          unit="%"
          showOutput
          value={current.scale}
          onValueChange={(next) => update({ scale: next })}
        />

        <JxSwitch
          label="Плотная вёрстка"
          checked={current.compact}
          onChange={(event) => update({ compact: event.target.checked })}
        />
      </div>

      <JxDivider label="Ещё" />

      <JxSelect
        label="Шрифт"
        placeholder="Выберите шрифт"
        options={FONTS}
        value={current.font}
        onValueChange={(next) => update({ font: next })}
      />
    </section>
  );
}