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

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

appearance-settings__none__claude-code__claude-opus-5__1 · 23.09.2026, 19:59:02 мск

Есть проблемы
Без контекста · claude-opus-5 · Claude Code
Контекст до задачи~154
Токенов на прогон11 069
Цена$0.067
Ошибок компилятора11

Первый вызов модели получил 8 646 токенов входа, включая системный промпт Claude Code. Контекст до задачи — оценка по длине текста, для русского текста она занижает число токенов в полтора-два раза.

Компилятор

  • Строка 3 · TS2724
    '"@jinx-ui/react"' has no exported member named 'Badge'. Did you mean 'JxBadge'?
  • Строка 4 · TS2724
    '"@jinx-ui/react"' has no exported member named 'Divider'. Did you mean 'JxDivider'?
  • Строка 5 · TS2305
    Module '"@jinx-ui/react"' has no exported member 'Field'.
  • Строка 6 · TS2305
    Module '"@jinx-ui/react"' has no exported member 'Heading'.
  • Строка 7 · TS2305
    Module '"@jinx-ui/react"' has no exported member 'Section'.
  • Строка 8 · TS2305
    Module '"@jinx-ui/react"' has no exported member 'SegmentedControl'.
  • Строка 9 · TS2724
    '"@jinx-ui/react"' has no exported member named 'Select'. Did you mean 'JxSelect'?
  • Строка 10 · TS2724
    '"@jinx-ui/react"' has no exported member named 'Slider'. Did you mean 'JxSlider'?
  • Строка 11 · TS2305
    Module '"@jinx-ui/react"' has no exported member 'Stack'.
  • Строка 12 · TS2724
    '"@jinx-ui/react"' has no exported member named 'Switch'. Did you mean 'JxSwitch'?
  • Строка 13 · TS2305
    Module '"@jinx-ui/react"' has no exported member 'Text'.
Выдуманные компоненты
BadgeDividerFieldHeadingSectionSegmentedControlSelectSliderStackSwitchText

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

  • inline-styleСтроки 74, 79, 104, 109
    Инлайновые стили вместо классов и токенов библиотеки
Компоненты библиотеки

Ни одного: код написан без библиотеки, поэтому прогон не засчитан

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

Контекст

Только формулировка задачи

Вход 2 · выход 2 423 · из кэша 8 233 · в кэш 411 · 26 с

Рендер

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

tsx139 строк
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
import { useState } from 'react';
import {
  Badge,
  Divider,
  Field,
  Heading,
  Section,
  SegmentedControl,
  Select,
  Slider,
  Stack,
  Switch,
  Text,
} from '@jinx-ui/react';

export type Theme = 'light' | 'dark' | 'system';
export type FontFamily = 'system' | 'inter' | 'mono';

export interface AppearanceValue {
  theme: Theme;
  scale: number;
  dense: boolean;
  font: FontFamily;
}

export interface AppearanceSettingsProps {
  initialValue?: Partial<AppearanceValue>;
  onChange: (value: AppearanceValue) => void;
}

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

const FONTS: { value: FontFamily; label: string }[] = [
  { value: 'system', label: 'Системный' },
  { value: 'inter', label: 'Inter' },
  { value: 'mono', label: 'Моноширинный' },
];

const DEFAULTS: AppearanceValue = {
  theme: 'system',
  scale: 100,
  dense: false,
  font: 'system',
};

export default function AppearanceSettings({
  initialValue,
  onChange,
}: AppearanceSettingsProps) {
  const [value, setValue] = useState<AppearanceValue>({
    ...DEFAULTS,
    ...initialValue,
  });

  const update = <K extends keyof AppearanceValue>(
    key: K,
    next: AppearanceValue[K],
  ) => {
    setValue((prev) => {
      const merged = { ...prev, [key]: next };
      onChange(merged);
      return merged;
    });
  };

  const themeLabel =
    THEMES.find((t) => t.value === value.theme)?.label ?? value.theme;

  return (
    <Section style={{ padding: 'var(--jx-space-6)' }}>
      <Stack
        direction="row"
        align="center"
        justify="between"
        style={{ marginBottom: 'var(--jx-space-5)' }}
      >
        <Heading level={2}>Внешний вид</Heading>
        <Badge variant="neutral">{themeLabel}</Badge>
      </Stack>

      <Stack direction="column" gap="var(--jx-space-5)">
        <Field label="Тема" htmlFor="appearance-theme">
          <SegmentedControl
            id="appearance-theme"
            value={value.theme}
            onChange={(next: Theme) => update('theme', next)}
            options={THEMES}
          />
        </Field>

        <Field label="Масштаб интерфейса" htmlFor="appearance-scale">
          <Stack direction="row" align="center" gap="var(--jx-space-4)">
            <Slider
              id="appearance-scale"
              min={80}
              max={140}
              step={5}
              value={value.scale}
              onChange={(next: number) => update('scale', next)}
              style={{ flex: 1 }}
            />
            <Text
              variant="body"
              tone="muted"
              style={{ minWidth: 'var(--jx-size-12)', textAlign: 'right' }}
            >
              {value.scale}%
            </Text>
          </Stack>
        </Field>

        <Switch
          checked={value.dense}
          onChange={(next: boolean) => update('dense', next)}
          label="Плотная вёрстка"
          description="Уменьшает отступы между элементами"
        />

        <Divider />

        <Stack direction="column" gap="var(--jx-space-4)">
          <Heading level={3}>Ещё</Heading>
          <Field label="Шрифт" htmlFor="appearance-font">
            <Select
              id="appearance-font"
              value={value.font}
              onChange={(next: FontFamily) => update('font', next)}
              options={FONTS}
            />
          </Field>
        </Stack>
      </Stack>
    </Section>
  );
}