vision-accessibility 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/dist/components/AccessibilityPanel/AccessibilityPanel.d.ts +12 -0
- package/dist/components/AccessibilityPanel/AccessibilityPanel.d.ts.map +1 -0
- package/dist/components/AccessibilityToggle/AccessibilityToggle.d.ts +18 -0
- package/dist/components/AccessibilityToggle/AccessibilityToggle.d.ts.map +1 -0
- package/dist/components/ColorSchemeControl/ColorSchemeControl.d.ts +18 -0
- package/dist/components/ColorSchemeControl/ColorSchemeControl.d.ts.map +1 -0
- package/dist/components/FontSizeControl/FontSizeControl.d.ts +18 -0
- package/dist/components/FontSizeControl/FontSizeControl.d.ts.map +1 -0
- package/dist/components/ImageControl/ImageControl.d.ts +18 -0
- package/dist/components/ImageControl/ImageControl.d.ts.map +1 -0
- package/dist/context/AccessibilityContext.d.ts +25 -0
- package/dist/context/AccessibilityContext.d.ts.map +1 -0
- package/dist/hooks/useAccessibilitySettings.d.ts +25 -0
- package/dist/hooks/useAccessibilitySettings.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +1151 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.umd.js +11 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/lib/accessibility-storage.d.ts +23 -0
- package/dist/lib/accessibility-storage.d.ts.map +1 -0
- package/dist/lib/css-applier.d.ts +50 -0
- package/dist/lib/css-applier.d.ts.map +1 -0
- package/dist/lib/dom-manipulator.d.ts +29 -0
- package/dist/lib/dom-manipulator.d.ts.map +1 -0
- package/dist/lib/font-size-manager.d.ts +26 -0
- package/dist/lib/font-size-manager.d.ts.map +1 -0
- package/dist/style.css +1 -0
- package/dist/types/index.d.ts +61 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/components/AccessibilityPanel/AccessibilityPanel.module.scss +214 -0
- package/src/components/AccessibilityPanel/AccessibilityPanel.tsx +151 -0
- package/src/components/AccessibilityToggle/AccessibilityToggle.module.scss +158 -0
- package/src/components/AccessibilityToggle/AccessibilityToggle.tsx +85 -0
- package/src/components/ColorSchemeControl/ColorSchemeControl.module.scss +175 -0
- package/src/components/ColorSchemeControl/ColorSchemeControl.tsx +116 -0
- package/src/components/FontSizeControl/FontSizeControl.module.scss +175 -0
- package/src/components/FontSizeControl/FontSizeControl.tsx +116 -0
- package/src/components/ImageControl/ImageControl.module.scss +163 -0
- package/src/components/ImageControl/ImageControl.tsx +85 -0
- package/src/context/AccessibilityContext.tsx +54 -0
- package/src/hooks/useAccessibilitySettings.ts +228 -0
- package/src/index.ts +80 -0
- package/src/lib/accessibility-storage.ts +82 -0
- package/src/lib/css-applier.ts +168 -0
- package/src/lib/dom-manipulator.ts +75 -0
- package/src/lib/font-size-manager.ts +185 -0
- package/src/styles/global.scss +60 -0
- package/src/styles/variables.scss +80 -0
- package/src/types/index.ts +72 -0
- package/src/types/scss.d.ts +9 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Стили для компонента главного переключателя режима доступности
|
|
3
|
+
* Реализует выделенный переключатель для управления общим режимом доступности
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.accessibilityToggle {
|
|
7
|
+
margin-bottom: $spacing-xl;
|
|
8
|
+
padding-bottom: $spacing-lg;
|
|
9
|
+
border-bottom: 1px solid $gray-300;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.toggleContainer {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: $spacing-sm;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.toggleLabel {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: $spacing-lg;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
padding: $spacing-lg;
|
|
24
|
+
border: 2px solid $primary;
|
|
25
|
+
border-radius: $border-radius-lg;
|
|
26
|
+
transition: all $transition-fast;
|
|
27
|
+
background: $primary-lighter;
|
|
28
|
+
|
|
29
|
+
&:hover {
|
|
30
|
+
background: $primary-light;
|
|
31
|
+
border-color: $primary-dark;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:focus-within {
|
|
35
|
+
background: $primary-light;
|
|
36
|
+
border-color: $primary-dark;
|
|
37
|
+
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.toggleInput {
|
|
42
|
+
position: absolute;
|
|
43
|
+
opacity: 0;
|
|
44
|
+
width: 0;
|
|
45
|
+
height: 0;
|
|
46
|
+
margin: 0;
|
|
47
|
+
|
|
48
|
+
&:focus + .toggleSlider {
|
|
49
|
+
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:checked + .toggleSlider {
|
|
53
|
+
background: $success;
|
|
54
|
+
|
|
55
|
+
.toggleThumb {
|
|
56
|
+
transform: translateX(28px);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.toggleSlider {
|
|
62
|
+
position: relative;
|
|
63
|
+
width: 56px;
|
|
64
|
+
height: 28px;
|
|
65
|
+
background: $gray-400;
|
|
66
|
+
border-radius: 14px;
|
|
67
|
+
transition: all $transition-normal;
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.toggleThumb {
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: 2px;
|
|
74
|
+
left: 2px;
|
|
75
|
+
width: 24px;
|
|
76
|
+
height: 24px;
|
|
77
|
+
background: $white;
|
|
78
|
+
border-radius: 50%;
|
|
79
|
+
transition: transform $transition-normal;
|
|
80
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.toggleContent {
|
|
84
|
+
flex: 1;
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
gap: $spacing-xs;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.toggleTitle {
|
|
91
|
+
font-size: 16px;
|
|
92
|
+
font-weight: 600;
|
|
93
|
+
color: $gray-800;
|
|
94
|
+
line-height: 1.3;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.toggleStatus {
|
|
98
|
+
font-size: 14px;
|
|
99
|
+
font-weight: 500;
|
|
100
|
+
color: $primary;
|
|
101
|
+
line-height: 1.2;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.description {
|
|
105
|
+
font-size: 13px;
|
|
106
|
+
color: $gray-600;
|
|
107
|
+
line-height: 1.4;
|
|
108
|
+
margin: 0;
|
|
109
|
+
padding: 0 $spacing-lg;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Высококонтрастная тема - адаптация стилей
|
|
113
|
+
:global(.accessibility-high-contrast) .accessibilityToggle {
|
|
114
|
+
border-bottom-color: $white !important;
|
|
115
|
+
|
|
116
|
+
.toggleLabel {
|
|
117
|
+
background: $black !important;
|
|
118
|
+
border-color: $white !important;
|
|
119
|
+
color: $white !important;
|
|
120
|
+
|
|
121
|
+
&:hover,
|
|
122
|
+
&:focus-within {
|
|
123
|
+
background: $gray-800 !important;
|
|
124
|
+
border-color: $yellow !important;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.toggleTitle {
|
|
129
|
+
color: $white !important;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.toggleStatus {
|
|
133
|
+
color: $yellow !important;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.description {
|
|
137
|
+
color: $gray-400 !important;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.toggleSlider {
|
|
141
|
+
background: $gray-600 !important;
|
|
142
|
+
border: 2px solid $white !important;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.toggleInput:checked + .toggleSlider {
|
|
146
|
+
background: $yellow !important;
|
|
147
|
+
|
|
148
|
+
.toggleThumb {
|
|
149
|
+
background: $black !important;
|
|
150
|
+
border: 2px solid $white !important;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.toggleThumb {
|
|
155
|
+
background: $white !important;
|
|
156
|
+
border: 2px solid $black !important;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Компонент главного переключателя режима доступности
|
|
3
|
+
* Предоставляет переключатель для включения/выключения всех настроек доступности
|
|
4
|
+
*
|
|
5
|
+
* Реализует требования:
|
|
6
|
+
* - 5.1: Включение режима доступности с активацией всех сохраненных настроек
|
|
7
|
+
* - 5.2: Выключение режима доступности с удалением всех CSS классов и стилей
|
|
8
|
+
* - 5.3: Возврат страницы к исходному состоянию без настроек доступности
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { useAccessibilityContext } from '../../context/AccessibilityContext';
|
|
13
|
+
import styles from './AccessibilityToggle.module.scss';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Свойства компонента AccessibilityToggle
|
|
17
|
+
*/
|
|
18
|
+
interface AccessibilityToggleProps {
|
|
19
|
+
/** CSS класс для дополнительной стилизации */
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Компонент главного переключателя режима доступности
|
|
25
|
+
*
|
|
26
|
+
* @param props - Свойства компонента
|
|
27
|
+
* @returns JSX элемент с переключателем общего режима доступности
|
|
28
|
+
*/
|
|
29
|
+
export const AccessibilityToggle: React.FC<AccessibilityToggleProps> = ({
|
|
30
|
+
className
|
|
31
|
+
}) => {
|
|
32
|
+
// Используем контекст для получения состояния и методов управления
|
|
33
|
+
const { settings, updateSettings } = useAccessibilityContext();
|
|
34
|
+
const isEnabled = settings.isEnabled;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Обработчик изменения общего режима доступности
|
|
38
|
+
*
|
|
39
|
+
* @param event - Событие изменения чекбокса
|
|
40
|
+
*/
|
|
41
|
+
const handleToggleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
42
|
+
updateSettings({ isEnabled: event.target.checked });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className={`${styles.accessibilityToggle} ${className || ''}`}>
|
|
47
|
+
<div className={styles.toggleContainer}>
|
|
48
|
+
<label className={styles.toggleLabel}>
|
|
49
|
+
<input
|
|
50
|
+
type="checkbox"
|
|
51
|
+
checked={isEnabled}
|
|
52
|
+
onChange={handleToggleChange}
|
|
53
|
+
className={styles.toggleInput}
|
|
54
|
+
aria-describedby="accessibility-toggle-description"
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<span className={styles.toggleSlider} aria-hidden="true">
|
|
58
|
+
<span className={styles.toggleThumb} />
|
|
59
|
+
</span>
|
|
60
|
+
|
|
61
|
+
<div className={styles.toggleContent}>
|
|
62
|
+
<span className={styles.toggleTitle}>
|
|
63
|
+
Режим доступности
|
|
64
|
+
</span>
|
|
65
|
+
<span className={styles.toggleStatus}>
|
|
66
|
+
{isEnabled ? 'Включен' : 'Выключен'}
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
</label>
|
|
70
|
+
|
|
71
|
+
<p
|
|
72
|
+
id="accessibility-toggle-description"
|
|
73
|
+
className={styles.description}
|
|
74
|
+
>
|
|
75
|
+
{isEnabled
|
|
76
|
+
? 'Все настройки доступности активны. Используйте элементы управления ниже для точной настройки.'
|
|
77
|
+
: 'Включите режим доступности для активации всех настроек. При выключении все изменения будут сброшены.'
|
|
78
|
+
}
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default AccessibilityToggle;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Стили для компонента управления цветовой схемой
|
|
3
|
+
* Реализует доступный интерфейс с радио-кнопками для выбора цветовой схемы
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.colorSchemeControl {
|
|
7
|
+
margin-bottom: $spacing-lg;
|
|
8
|
+
|
|
9
|
+
&.disabled {
|
|
10
|
+
.title {
|
|
11
|
+
opacity: 0.6;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.title {
|
|
17
|
+
font-size: 14px;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
margin: 0 0 $spacing-md 0;
|
|
20
|
+
color: $gray-800;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.optionsContainer {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: $spacing-sm;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.option {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: flex-start;
|
|
32
|
+
gap: $spacing-md;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
padding: $spacing-md;
|
|
35
|
+
border: 1px solid $gray-300;
|
|
36
|
+
border-radius: $border-radius;
|
|
37
|
+
transition: all $transition-fast;
|
|
38
|
+
position: relative;
|
|
39
|
+
background: $white;
|
|
40
|
+
|
|
41
|
+
&:hover {
|
|
42
|
+
background: $gray-100;
|
|
43
|
+
border-color: $primary;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:focus-within {
|
|
47
|
+
background: $gray-100;
|
|
48
|
+
border-color: $primary;
|
|
49
|
+
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&.selected {
|
|
53
|
+
background: $primary-light;
|
|
54
|
+
border-color: $primary;
|
|
55
|
+
|
|
56
|
+
.radioIndicator {
|
|
57
|
+
background: $primary;
|
|
58
|
+
border-color: $primary;
|
|
59
|
+
|
|
60
|
+
&::after {
|
|
61
|
+
opacity: 1;
|
|
62
|
+
transform: scale(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&.optionDisabled {
|
|
68
|
+
opacity: 0.5;
|
|
69
|
+
cursor: not-allowed;
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
|
|
72
|
+
&:hover {
|
|
73
|
+
background: $white;
|
|
74
|
+
border-color: $gray-300;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.radioInput {
|
|
80
|
+
position: absolute;
|
|
81
|
+
opacity: 0;
|
|
82
|
+
width: 0;
|
|
83
|
+
height: 0;
|
|
84
|
+
margin: 0;
|
|
85
|
+
|
|
86
|
+
&:focus + .optionContent {
|
|
87
|
+
outline: 2px solid $primary;
|
|
88
|
+
outline-offset: 2px;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.optionContent {
|
|
93
|
+
flex: 1;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: $spacing-xs;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.optionLabel {
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
font-weight: 500;
|
|
102
|
+
color: $gray-800;
|
|
103
|
+
line-height: 1.4;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.optionDescription {
|
|
107
|
+
font-size: 12px;
|
|
108
|
+
color: $gray-600;
|
|
109
|
+
line-height: 1.3;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.radioIndicator {
|
|
113
|
+
width: 18px;
|
|
114
|
+
height: 18px;
|
|
115
|
+
border: 2px solid $gray-400;
|
|
116
|
+
border-radius: 50%;
|
|
117
|
+
background: $white;
|
|
118
|
+
transition: all $transition-fast;
|
|
119
|
+
position: relative;
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
margin-top: 1px;
|
|
122
|
+
|
|
123
|
+
&::after {
|
|
124
|
+
content: '';
|
|
125
|
+
position: absolute;
|
|
126
|
+
top: 50%;
|
|
127
|
+
left: 50%;
|
|
128
|
+
width: 8px;
|
|
129
|
+
height: 8px;
|
|
130
|
+
background: $white;
|
|
131
|
+
border-radius: 50%;
|
|
132
|
+
transform: translate(-50%, -50%) scale(0);
|
|
133
|
+
transition: all $transition-fast;
|
|
134
|
+
opacity: 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Высококонтрастная тема - адаптация стилей
|
|
139
|
+
:global(.accessibility-high-contrast) .colorSchemeControl {
|
|
140
|
+
.title {
|
|
141
|
+
color: $white !important;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.option {
|
|
145
|
+
background: $black !important;
|
|
146
|
+
border-color: $white !important;
|
|
147
|
+
color: $white !important;
|
|
148
|
+
|
|
149
|
+
&:hover,
|
|
150
|
+
&:focus-within {
|
|
151
|
+
background: $gray-800 !important;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
&.selected {
|
|
155
|
+
background: $gray-800 !important;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.optionLabel {
|
|
160
|
+
color: $white !important;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.optionDescription {
|
|
164
|
+
color: $gray-400 !important;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.radioIndicator {
|
|
168
|
+
border-color: $white !important;
|
|
169
|
+
background: $black !important;
|
|
170
|
+
|
|
171
|
+
&::after {
|
|
172
|
+
background: $white !important;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Компонент управления цветовой схемой
|
|
3
|
+
* Предоставляет радио-кнопки для выбора цветовой схемы доступности
|
|
4
|
+
*
|
|
5
|
+
* Реализует требования:
|
|
6
|
+
* - 2.1: Применение высококонтрастной цветовой схемы
|
|
7
|
+
* - 2.2: Применение черно-белой цветовой схемы
|
|
8
|
+
* - 2.3: Возврат к стандартной цветовой схеме
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { ColorScheme } from '../../types';
|
|
13
|
+
import { useAccessibilityContext } from '../../context/AccessibilityContext';
|
|
14
|
+
import styles from './ColorSchemeControl.module.scss';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Свойства компонента ColorSchemeControl
|
|
18
|
+
*/
|
|
19
|
+
interface ColorSchemeControlProps {
|
|
20
|
+
/** CSS класс для дополнительной стилизации */
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Опции цветовых схем с описаниями
|
|
26
|
+
*/
|
|
27
|
+
const COLOR_SCHEME_OPTIONS: Array<{
|
|
28
|
+
value: ColorScheme;
|
|
29
|
+
label: string;
|
|
30
|
+
description: string;
|
|
31
|
+
}> = [
|
|
32
|
+
{
|
|
33
|
+
value: 'standard',
|
|
34
|
+
label: 'Стандартный',
|
|
35
|
+
description: 'Обычная цветовая схема сайта'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
value: 'high-contrast',
|
|
39
|
+
label: 'Контрастный белый на черном',
|
|
40
|
+
description: 'Высококонтрастная схема для лучшей читаемости'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
value: 'grayscale',
|
|
44
|
+
label: 'Черно-белый',
|
|
45
|
+
description: 'Преобразование всех цветов в оттенки серого'
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Компонент управления цветовой схемой
|
|
51
|
+
*
|
|
52
|
+
* @param props - Свойства компонента
|
|
53
|
+
* @returns JSX элемент с радио-кнопками для выбора цветовой схемы
|
|
54
|
+
*/
|
|
55
|
+
export const ColorSchemeControl: React.FC<ColorSchemeControlProps> = ({
|
|
56
|
+
className
|
|
57
|
+
}) => {
|
|
58
|
+
// Используем контекст для получения состояния и методов управления
|
|
59
|
+
const { settings, updateSettings } = useAccessibilityContext();
|
|
60
|
+
const colorScheme = settings.colorScheme;
|
|
61
|
+
const isEnabled = settings.isEnabled;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Обработчик изменения цветовой схемы
|
|
65
|
+
*
|
|
66
|
+
* @param event - Событие изменения радио-кнопки
|
|
67
|
+
*/
|
|
68
|
+
const handleColorSchemeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
69
|
+
const newColorScheme = event.target.value as ColorScheme;
|
|
70
|
+
updateSettings({ colorScheme: newColorScheme });
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className={`${styles.colorSchemeControl} ${className || ''} ${!isEnabled ? styles.disabled : ''}`}>
|
|
75
|
+
<h4 className={styles.title}>
|
|
76
|
+
Цветовая схема
|
|
77
|
+
</h4>
|
|
78
|
+
|
|
79
|
+
<div className={styles.optionsContainer} role="radiogroup" aria-labelledby="color-scheme-title">
|
|
80
|
+
{COLOR_SCHEME_OPTIONS.map((option) => (
|
|
81
|
+
<label
|
|
82
|
+
key={option.value}
|
|
83
|
+
className={`${styles.option} ${colorScheme === option.value ? styles.selected : ''} ${!isEnabled ? styles.optionDisabled : ''}`}
|
|
84
|
+
>
|
|
85
|
+
<input
|
|
86
|
+
type="radio"
|
|
87
|
+
name="colorScheme"
|
|
88
|
+
value={option.value}
|
|
89
|
+
checked={colorScheme === option.value}
|
|
90
|
+
onChange={handleColorSchemeChange}
|
|
91
|
+
className={styles.radioInput}
|
|
92
|
+
aria-describedby={`color-scheme-${option.value}-description`}
|
|
93
|
+
disabled={!isEnabled}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<div className={styles.optionContent}>
|
|
97
|
+
<span className={styles.optionLabel}>
|
|
98
|
+
{option.label}
|
|
99
|
+
</span>
|
|
100
|
+
<span
|
|
101
|
+
id={`color-scheme-${option.value}-description`}
|
|
102
|
+
className={styles.optionDescription}
|
|
103
|
+
>
|
|
104
|
+
{option.description}
|
|
105
|
+
</span>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div className={styles.radioIndicator} aria-hidden="true" />
|
|
109
|
+
</label>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default ColorSchemeControl;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Стили для компонента управления размером шрифта
|
|
3
|
+
* Реализует доступный интерфейс с радио-кнопками для выбора размера шрифта
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.fontSizeControl {
|
|
7
|
+
margin-bottom: $spacing-lg;
|
|
8
|
+
|
|
9
|
+
&.disabled {
|
|
10
|
+
.title {
|
|
11
|
+
opacity: 0.6;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.title {
|
|
17
|
+
font-size: 14px;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
margin: 0 0 $spacing-md 0;
|
|
20
|
+
color: $gray-800;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.optionsContainer {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: $spacing-sm;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.option {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: flex-start;
|
|
32
|
+
gap: $spacing-md;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
padding: $spacing-md;
|
|
35
|
+
border: 1px solid $gray-300;
|
|
36
|
+
border-radius: $border-radius;
|
|
37
|
+
transition: all $transition-fast;
|
|
38
|
+
position: relative;
|
|
39
|
+
background: $white;
|
|
40
|
+
|
|
41
|
+
&:hover {
|
|
42
|
+
background: $gray-100;
|
|
43
|
+
border-color: $primary;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:focus-within {
|
|
47
|
+
background: $gray-100;
|
|
48
|
+
border-color: $primary;
|
|
49
|
+
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&.selected {
|
|
53
|
+
background: $primary-light;
|
|
54
|
+
border-color: $primary;
|
|
55
|
+
|
|
56
|
+
.radioIndicator {
|
|
57
|
+
background: $primary;
|
|
58
|
+
border-color: $primary;
|
|
59
|
+
|
|
60
|
+
&::after {
|
|
61
|
+
opacity: 1;
|
|
62
|
+
transform: scale(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&.optionDisabled {
|
|
68
|
+
opacity: 0.5;
|
|
69
|
+
cursor: not-allowed;
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
|
|
72
|
+
&:hover {
|
|
73
|
+
background: $white;
|
|
74
|
+
border-color: $gray-300;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.radioInput {
|
|
80
|
+
position: absolute;
|
|
81
|
+
opacity: 0;
|
|
82
|
+
width: 0;
|
|
83
|
+
height: 0;
|
|
84
|
+
margin: 0;
|
|
85
|
+
|
|
86
|
+
&:focus + .optionContent {
|
|
87
|
+
outline: 2px solid $primary;
|
|
88
|
+
outline-offset: 2px;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.optionContent {
|
|
93
|
+
flex: 1;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: $spacing-xs;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.optionLabel {
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
font-weight: 500;
|
|
102
|
+
color: $gray-800;
|
|
103
|
+
line-height: 1.4;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.optionDescription {
|
|
107
|
+
font-size: 12px;
|
|
108
|
+
color: $gray-600;
|
|
109
|
+
line-height: 1.3;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.radioIndicator {
|
|
113
|
+
width: 18px;
|
|
114
|
+
height: 18px;
|
|
115
|
+
border: 2px solid $gray-400;
|
|
116
|
+
border-radius: 50%;
|
|
117
|
+
background: $white;
|
|
118
|
+
transition: all $transition-fast;
|
|
119
|
+
position: relative;
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
margin-top: 1px;
|
|
122
|
+
|
|
123
|
+
&::after {
|
|
124
|
+
content: '';
|
|
125
|
+
position: absolute;
|
|
126
|
+
top: 50%;
|
|
127
|
+
left: 50%;
|
|
128
|
+
width: 8px;
|
|
129
|
+
height: 8px;
|
|
130
|
+
background: $white;
|
|
131
|
+
border-radius: 50%;
|
|
132
|
+
transform: translate(-50%, -50%) scale(0);
|
|
133
|
+
transition: all $transition-fast;
|
|
134
|
+
opacity: 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Высококонтрастная тема - адаптация стилей
|
|
139
|
+
:global(.accessibility-high-contrast) .fontSizeControl {
|
|
140
|
+
.title {
|
|
141
|
+
color: $white !important;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.option {
|
|
145
|
+
background: $black !important;
|
|
146
|
+
border-color: $white !important;
|
|
147
|
+
color: $white !important;
|
|
148
|
+
|
|
149
|
+
&:hover,
|
|
150
|
+
&:focus-within {
|
|
151
|
+
background: $gray-800 !important;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
&.selected {
|
|
155
|
+
background: $gray-800 !important;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.optionLabel {
|
|
160
|
+
color: $white !important;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.optionDescription {
|
|
164
|
+
color: $gray-400 !important;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.radioIndicator {
|
|
168
|
+
border-color: $white !important;
|
|
169
|
+
background: $black !important;
|
|
170
|
+
|
|
171
|
+
&::after {
|
|
172
|
+
background: $white !important;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|