@formixru/b2b-ui 4.0.0 → 4.2.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/package.json
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
@use '../../variables' as *;
|
|
2
|
+
|
|
3
|
+
$b: '.search-input';
|
|
4
|
+
|
|
5
|
+
#{$b} {
|
|
6
|
+
position: relative;
|
|
7
|
+
|
|
8
|
+
&__submit {
|
|
9
|
+
align-items: center;
|
|
10
|
+
display: flex;
|
|
11
|
+
height: 24px;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
left: 12px;
|
|
14
|
+
position: absolute;
|
|
15
|
+
top: calc(50% - 12px);
|
|
16
|
+
width: 24px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&__submit-icon {
|
|
20
|
+
fill: $search-input-submit-icon-color;
|
|
21
|
+
height: 16px;
|
|
22
|
+
width: 16px;
|
|
23
|
+
|
|
24
|
+
#{$b}__input:not(:placeholder-shown) ~ #{$b}__submit & {
|
|
25
|
+
fill: $search-input-submit-icon-active-color;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&__spinner {
|
|
30
|
+
container-type: size;
|
|
31
|
+
height: 16px;
|
|
32
|
+
width: 16px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&__input {
|
|
36
|
+
padding-left: 44px;
|
|
37
|
+
width: 100%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&__clear {
|
|
41
|
+
align-items: center;
|
|
42
|
+
background-color: $search-input-clear-bg;
|
|
43
|
+
border-bottom-right-radius: $search-input-clear-radius;
|
|
44
|
+
border-top-right-radius: $search-input-clear-radius;
|
|
45
|
+
color: $search-input-clear-color;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
display: flex;
|
|
48
|
+
height: $input-height - $input-border-width * 2;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
position: absolute;
|
|
51
|
+
right: 1px;
|
|
52
|
+
top: 1px;
|
|
53
|
+
transition-duration: var(--duration-micro-s);
|
|
54
|
+
transition-property: color;
|
|
55
|
+
transition-timing-function: var(--easing-standard);
|
|
56
|
+
width: $input-height - $input-border-width * 2;
|
|
57
|
+
|
|
58
|
+
.input_small ~ & {
|
|
59
|
+
height: $input-small-height - $input-border-width * 2;
|
|
60
|
+
width: $input-small-height - $input-border-width * 2;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&:hover,
|
|
64
|
+
&:focus-visible {
|
|
65
|
+
color: $search-input-clear-active-color;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&__clear-icon {
|
|
70
|
+
fill: currentcolor;
|
|
71
|
+
height: 16px;
|
|
72
|
+
width: 16px;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="$root" class="search-input">
|
|
3
|
+
<input
|
|
4
|
+
ref="$input"
|
|
5
|
+
v-model="query"
|
|
6
|
+
class="search-input__input input"
|
|
7
|
+
:class="props.inputCssClasses"
|
|
8
|
+
:name="props.name"
|
|
9
|
+
type="text"
|
|
10
|
+
:placeholder="props.placeholder"
|
|
11
|
+
autocomplete="off"
|
|
12
|
+
@focusin="emit('focusin', $event)"
|
|
13
|
+
@focusout="emit('focusout', $event)"
|
|
14
|
+
@keydown="emit('keydown', $event)"
|
|
15
|
+
/>
|
|
16
|
+
<button
|
|
17
|
+
class="search-input__submit"
|
|
18
|
+
type="button"
|
|
19
|
+
@click="
|
|
20
|
+
updateModel();
|
|
21
|
+
emit('submit');
|
|
22
|
+
"
|
|
23
|
+
>
|
|
24
|
+
<span v-show="props.isLoading" class="search-input__spinner">
|
|
25
|
+
<span class="spinner"></span>
|
|
26
|
+
</span>
|
|
27
|
+
<svg v-show="!props.isLoading" class="search-input__submit-icon">
|
|
28
|
+
<use :href="`${ICONS_PATH}#search`" />
|
|
29
|
+
</svg>
|
|
30
|
+
</button>
|
|
31
|
+
<button v-show="query.length > 0" class="search-input__clear" type="button" @click="query = ''">
|
|
32
|
+
<svg class="search-input__clear-icon"><use :href="`${ICONS_PATH}#close`" /></svg>
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup>
|
|
38
|
+
import { cssVar } from '@advdominion/css-var';
|
|
39
|
+
import { debounce } from 'es-toolkit';
|
|
40
|
+
import { ref, useTemplateRef, watch, watchEffect } from 'vue';
|
|
41
|
+
|
|
42
|
+
const ICONS_PATH = window.ICONS_PATH;
|
|
43
|
+
|
|
44
|
+
const emit = defineEmits(['focusin', 'focusout', 'keydown', 'submit']);
|
|
45
|
+
const model = defineModel({ type: String, default: '' });
|
|
46
|
+
|
|
47
|
+
const props = defineProps({
|
|
48
|
+
name: {
|
|
49
|
+
type: String,
|
|
50
|
+
default: '',
|
|
51
|
+
},
|
|
52
|
+
placeholder: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: 'Поиск',
|
|
55
|
+
},
|
|
56
|
+
minlength: {
|
|
57
|
+
type: Number,
|
|
58
|
+
default: 3,
|
|
59
|
+
},
|
|
60
|
+
inputDelay: {
|
|
61
|
+
type: Number,
|
|
62
|
+
default() {
|
|
63
|
+
return cssVar('--input-delay');
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
inputCssClasses: {
|
|
67
|
+
type: Array,
|
|
68
|
+
default() {
|
|
69
|
+
return [];
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
isLoading: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const $root = useTemplateRef('$root');
|
|
78
|
+
const $input = useTemplateRef('$input');
|
|
79
|
+
|
|
80
|
+
const query = ref(model.value);
|
|
81
|
+
watchEffect(() => {
|
|
82
|
+
query.value = model.value;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const debouncedUpdateModel = debounce(() => {
|
|
86
|
+
model.value = query.value;
|
|
87
|
+
}, props.inputDelay);
|
|
88
|
+
|
|
89
|
+
const updateModel = () => {
|
|
90
|
+
debouncedUpdateModel.cancel();
|
|
91
|
+
|
|
92
|
+
if (query.value.length === 0) {
|
|
93
|
+
model.value = query.value;
|
|
94
|
+
} else if (query.value.length >= props.minlength) {
|
|
95
|
+
debouncedUpdateModel();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
watch(query, (newQueryValue) => {
|
|
100
|
+
if (newQueryValue !== model.value) {
|
|
101
|
+
updateModel();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const focus = () => {
|
|
106
|
+
$input.value.focus();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
defineExpose({
|
|
110
|
+
$root,
|
|
111
|
+
focus,
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
@@ -109,9 +109,6 @@ $b: '.suggest-input';
|
|
|
109
109
|
display: flex;
|
|
110
110
|
min-height: $suggest-input-item-height;
|
|
111
111
|
padding: 4px $input-padding-x;
|
|
112
|
-
transition-duration: var(--duration-micro-s);
|
|
113
|
-
transition-property: background-color, color;
|
|
114
|
-
transition-timing-function: var(--easing-standard);
|
|
115
112
|
|
|
116
113
|
@include font($suggest-input-item-font);
|
|
117
114
|
|
|
@@ -121,6 +118,11 @@ $b: '.suggest-input';
|
|
|
121
118
|
color: $suggest-input-item-active-color;
|
|
122
119
|
}
|
|
123
120
|
|
|
121
|
+
&_disabled {
|
|
122
|
+
color: $suggest-input-item-disabled-color;
|
|
123
|
+
pointer-events: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
124
126
|
mark {
|
|
125
127
|
background-color: transparent;
|
|
126
128
|
color: $suggest-input-item-mark-color;
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="$root" class="suggest-input" @keydown.esc="esc()">
|
|
3
|
+
<SearchInput
|
|
4
|
+
v-if="props.searchInput"
|
|
5
|
+
v-model="model"
|
|
6
|
+
class="suggest-input__input"
|
|
7
|
+
:class="[showSuggests ? 'suggest-input__input_active' : '']"
|
|
8
|
+
:placeholder="props.placeholder"
|
|
9
|
+
:minlength="0"
|
|
10
|
+
:input-delay="0"
|
|
11
|
+
:input-css-classes="props.inputCssClasses"
|
|
12
|
+
:is-loading="isLoading"
|
|
13
|
+
@update:model-value="handleInput()"
|
|
14
|
+
@focusin="focusin()"
|
|
15
|
+
@focusout="focusout($event)"
|
|
16
|
+
@keydown="arrowNavigation($event)"
|
|
17
|
+
/>
|
|
3
18
|
<input
|
|
19
|
+
v-else
|
|
4
20
|
:id="props.id"
|
|
5
|
-
ref="$input"
|
|
6
21
|
v-model="model"
|
|
7
22
|
class="suggest-input__input input"
|
|
8
|
-
:class="[
|
|
9
|
-
showSuggests ? 'suggest-input__input_active' : '',
|
|
10
|
-
props.small ? 'input_small' : '',
|
|
11
|
-
props.error ? 'input_error' : '',
|
|
12
|
-
...props.inputCssClasses,
|
|
13
|
-
]"
|
|
23
|
+
:class="[showSuggests ? 'suggest-input__input_active' : '', ...props.inputCssClasses]"
|
|
14
24
|
type="text"
|
|
15
25
|
:name="props.name"
|
|
16
26
|
:required="props.required"
|
|
@@ -22,8 +32,14 @@
|
|
|
22
32
|
@focusout="focusout($event)"
|
|
23
33
|
@keydown="arrowNavigation($event)"
|
|
24
34
|
/>
|
|
25
|
-
<div v-show="isLoading" class="suggest-input__loader"><div class="spinner"></div></div>
|
|
26
|
-
<button
|
|
35
|
+
<div v-show="isLoading && !props.searchInput" class="suggest-input__loader"><div class="spinner"></div></div>
|
|
36
|
+
<button
|
|
37
|
+
v-show="isClearVisible && !props.searchInput"
|
|
38
|
+
ref="$clear"
|
|
39
|
+
class="suggest-input__clear"
|
|
40
|
+
type="button"
|
|
41
|
+
@click="clear()"
|
|
42
|
+
>
|
|
27
43
|
<svg class="suggest-input__clear-icon"><use :href="`${ICONS_PATH}#close`" /></svg>
|
|
28
44
|
</button>
|
|
29
45
|
<div v-show="showSuggests" ref="$list" class="suggest-input__list" @mouseover="focusSuggestIndex = -1">
|
|
@@ -32,7 +48,10 @@
|
|
|
32
48
|
v-for="(item, index) in formattedSuggests"
|
|
33
49
|
:key="index"
|
|
34
50
|
class="suggest-input__list-item"
|
|
35
|
-
:class="{
|
|
51
|
+
:class="{
|
|
52
|
+
'suggest-input__list-item_focus': index === focusSuggestIndex,
|
|
53
|
+
'suggest-input__list-item_disabled': item.disabled,
|
|
54
|
+
}"
|
|
36
55
|
tabindex="0"
|
|
37
56
|
@click="select(item)"
|
|
38
57
|
>
|
|
@@ -52,6 +71,7 @@ import { debounce } from 'es-toolkit';
|
|
|
52
71
|
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';
|
|
53
72
|
|
|
54
73
|
import { request } from '../request/index.js';
|
|
74
|
+
import SearchInput from '../search-input/index.vue';
|
|
55
75
|
|
|
56
76
|
const ICONS_PATH = window.ICONS_PATH;
|
|
57
77
|
|
|
@@ -109,12 +129,6 @@ const props = defineProps({
|
|
|
109
129
|
type: String,
|
|
110
130
|
default: 'По вашему запросу ничего не найдено',
|
|
111
131
|
},
|
|
112
|
-
small: {
|
|
113
|
-
type: Boolean,
|
|
114
|
-
},
|
|
115
|
-
error: {
|
|
116
|
-
type: Boolean,
|
|
117
|
-
},
|
|
118
132
|
loaderDelay: {
|
|
119
133
|
type: Number,
|
|
120
134
|
default() {
|
|
@@ -133,10 +147,12 @@ const props = defineProps({
|
|
|
133
147
|
return cssVar('--fallback-timeout');
|
|
134
148
|
},
|
|
135
149
|
},
|
|
150
|
+
searchInput: {
|
|
151
|
+
type: Boolean,
|
|
152
|
+
},
|
|
136
153
|
});
|
|
137
154
|
|
|
138
155
|
const $root = useTemplateRef('$root');
|
|
139
|
-
const $input = useTemplateRef('$input');
|
|
140
156
|
const $list = useTemplateRef('$list');
|
|
141
157
|
const $clear = useTemplateRef('$clear');
|
|
142
158
|
|
|
@@ -157,43 +173,13 @@ watch(suggests, () => {
|
|
|
157
173
|
});
|
|
158
174
|
|
|
159
175
|
const showSuggests = ref(false);
|
|
160
|
-
const itemHeight = ref(0);
|
|
161
176
|
|
|
162
177
|
watch(showSuggests, async (value) => {
|
|
163
|
-
if (value
|
|
164
|
-
await nextTick();
|
|
165
|
-
itemHeight.value = $list.value.querySelector('.suggest-input__list-item').offsetHeight;
|
|
166
|
-
} else {
|
|
178
|
+
if (!value) {
|
|
167
179
|
suggests.value = [];
|
|
168
|
-
await nextTick();
|
|
169
|
-
itemHeight.value = 0;
|
|
170
180
|
}
|
|
171
181
|
});
|
|
172
182
|
|
|
173
|
-
const maxItemsWithoutScroll = computed(() => {
|
|
174
|
-
if (itemHeight.value) {
|
|
175
|
-
return Math.floor($list.value.offsetHeight / itemHeight.value);
|
|
176
|
-
}
|
|
177
|
-
return 0;
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const indexesToScroll = computed(() => {
|
|
181
|
-
if (maxItemsWithoutScroll.value) {
|
|
182
|
-
const indexes = [0];
|
|
183
|
-
for (const index of suggests.value.keys()) {
|
|
184
|
-
const nextIndex = indexes.at(-1) + maxItemsWithoutScroll.value;
|
|
185
|
-
if (nextIndex > suggests.value.length) {
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
if (index === nextIndex) {
|
|
189
|
-
indexes.push(index);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return indexes;
|
|
193
|
-
}
|
|
194
|
-
return [];
|
|
195
|
-
});
|
|
196
|
-
|
|
197
183
|
const isLoading = ref(false);
|
|
198
184
|
const isClearVisible = computed(() => model.value.length >= props.minlength && !isLoading.value);
|
|
199
185
|
|
|
@@ -306,60 +292,88 @@ const clear = () => {
|
|
|
306
292
|
showSuggests.value = false;
|
|
307
293
|
};
|
|
308
294
|
|
|
309
|
-
const select = (
|
|
295
|
+
const select = (item) => {
|
|
296
|
+
if (item.disabled) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
310
300
|
cancelRequest();
|
|
311
|
-
model.value = label;
|
|
301
|
+
model.value = item.label;
|
|
312
302
|
showSuggests.value = false;
|
|
313
303
|
emit('select', {
|
|
314
304
|
input: model.value,
|
|
315
|
-
value,
|
|
305
|
+
value: item.value,
|
|
316
306
|
});
|
|
317
307
|
};
|
|
318
308
|
|
|
319
|
-
const arrowNavigation = (event) => {
|
|
309
|
+
const arrowNavigation = async (event) => {
|
|
320
310
|
if (showSuggests.value) {
|
|
321
311
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
312
|
+
event.preventDefault();
|
|
313
|
+
|
|
314
|
+
const len = suggests.value.length;
|
|
315
|
+
if (len === 0) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
327
319
|
switch (event.key) {
|
|
328
320
|
case 'ArrowDown': {
|
|
329
|
-
focusSuggestIndex.value
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
$list.value.scrollTop += maxItemsWithoutScroll.value * itemHeight.value;
|
|
337
|
-
} else if (focusSuggestIndex.value === 0) {
|
|
338
|
-
$list.value.scrollTop = 0;
|
|
321
|
+
let nextIndex = focusSuggestIndex.value;
|
|
322
|
+
for (let i = 0; i < len; i++) {
|
|
323
|
+
nextIndex = (nextIndex + 1) % len;
|
|
324
|
+
if (!suggests.value[nextIndex]?.disabled) {
|
|
325
|
+
focusSuggestIndex.value = nextIndex;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
339
328
|
}
|
|
340
329
|
break;
|
|
341
330
|
}
|
|
342
331
|
case 'ArrowUp': {
|
|
343
|
-
focusSuggestIndex.value
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
$list.value.scrollTop -= maxItemsWithoutScroll.value * itemHeight.value;
|
|
351
|
-
} else if (focusSuggestIndex.value === suggests.value.length - 1) {
|
|
352
|
-
$list.value.scrollTop = suggests.value.length * itemHeight.value;
|
|
332
|
+
let nextIndex = focusSuggestIndex.value === -1 ? 0 : focusSuggestIndex.value;
|
|
333
|
+
for (let i = 0; i < len; i++) {
|
|
334
|
+
nextIndex = (nextIndex - 1 + len) % len;
|
|
335
|
+
if (!suggests.value[nextIndex]?.disabled) {
|
|
336
|
+
focusSuggestIndex.value = nextIndex;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
353
339
|
}
|
|
354
340
|
break;
|
|
355
341
|
}
|
|
356
342
|
}
|
|
343
|
+
|
|
344
|
+
await nextTick();
|
|
345
|
+
|
|
346
|
+
const activeElement = $list.value?.querySelectorAll('.suggest-input__list-item')[focusSuggestIndex.value];
|
|
347
|
+
if (activeElement && $list.value) {
|
|
348
|
+
const containerRect = $list.value.getBoundingClientRect();
|
|
349
|
+
const elemRect = activeElement.getBoundingClientRect();
|
|
350
|
+
|
|
351
|
+
// У $list задан paddint-top, получаем его значение
|
|
352
|
+
const paddingTop = Number.parseInt(getComputedStyle($list.value).paddingTop) || 0;
|
|
353
|
+
// Верхняя видимая граница = реальные координаты + отступ
|
|
354
|
+
const visibleTop = containerRect.top + paddingTop;
|
|
355
|
+
|
|
356
|
+
if (focusSuggestIndex.value === 0) {
|
|
357
|
+
$list.value.scrollTop = 0;
|
|
358
|
+
} else if (focusSuggestIndex.value === suggests.value.length - 1) {
|
|
359
|
+
$list.value.scrollTop = $list.value.scrollHeight;
|
|
360
|
+
} else if (elemRect.top < visibleTop) {
|
|
361
|
+
$list.value.scrollTop -= visibleTop - elemRect.top;
|
|
362
|
+
} else if (elemRect.bottom > containerRect.bottom) {
|
|
363
|
+
$list.value.scrollTop += elemRect.bottom - containerRect.bottom;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
357
366
|
}
|
|
367
|
+
|
|
358
368
|
if (event.key === 'Enter' && focusSuggestIndex.value >= 0) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
369
|
+
const item = suggests.value[focusSuggestIndex.value];
|
|
370
|
+
|
|
371
|
+
if (item && !item.disabled) {
|
|
372
|
+
// Ждём, чтобы событие нажатия Enter сработало у других обработчиков (например, в формах)
|
|
373
|
+
setTimeout(() => {
|
|
374
|
+
select(item);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
363
377
|
}
|
|
364
378
|
}
|
|
365
379
|
};
|
package/src/variables.scss
CHANGED
|
@@ -581,8 +581,18 @@ $input-textarea-small-min-height: 72px !default;
|
|
|
581
581
|
|
|
582
582
|
/********** Поле ввода пароля (.password) **********/
|
|
583
583
|
|
|
584
|
-
$password-color: $color-grey-40;
|
|
585
|
-
$password-active-color: $color-primary-40;
|
|
584
|
+
$password-color: $color-grey-40 !default;
|
|
585
|
+
$password-active-color: $color-primary-40 !default;
|
|
586
|
+
|
|
587
|
+
/********** Поле поиска (.search-input) **********/
|
|
588
|
+
|
|
589
|
+
$search-input-submit-icon-color: $color-grey-70 !default;
|
|
590
|
+
$search-input-submit-icon-active-color: $color-error !default;
|
|
591
|
+
|
|
592
|
+
$search-input-clear-bg: $color-white !default;
|
|
593
|
+
$search-input-clear-radius: $input-radius !default;
|
|
594
|
+
$search-input-clear-color: $color-grey-70 !default;
|
|
595
|
+
$search-input-clear-active-color: $color-grey-40 !default;
|
|
586
596
|
|
|
587
597
|
/********** Поле ввода с подсказкамми (.suggest-input) **********/
|
|
588
598
|
|
|
@@ -592,6 +602,8 @@ $suggest-input-item-font: ('font-size': $font-size, 'line-height': $line-height)
|
|
|
592
602
|
$suggest-input-item-mark-color: inherit !default;
|
|
593
603
|
$suggest-input-item-mark-font: ('font-weight': bold) !default;
|
|
594
604
|
|
|
605
|
+
$suggest-input-item-disabled-color: $font-color-tertiary !default;
|
|
606
|
+
|
|
595
607
|
$suggest-input-item-active-bg: $color-grey-90 !default;
|
|
596
608
|
$suggest-input-item-active-color: $font-color-action !default;
|
|
597
609
|
|