@formixru/b2b-ui 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.
Files changed (73) hide show
  1. package/CONTRIBUTING.md +61 -0
  2. package/README.md +30 -0
  3. package/package.json +110 -0
  4. package/src/base.scss +89 -0
  5. package/src/components/action-list/index.scss +38 -0
  6. package/src/components/banner/index.scss +30 -0
  7. package/src/components/breadcrumbs/index.js +8 -0
  8. package/src/components/breadcrumbs/index.scss +55 -0
  9. package/src/components/button/index.scss +223 -0
  10. package/src/components/checkbox/index.scss +94 -0
  11. package/src/components/color-input/index.scss +88 -0
  12. package/src/components/color-input/index.vue +156 -0
  13. package/src/components/date-input/index.scss +126 -0
  14. package/src/components/date-input/index.vue +240 -0
  15. package/src/components/delete-all/index.scss +44 -0
  16. package/src/components/dialog/index.js +221 -0
  17. package/src/components/dialog/index.scss +198 -0
  18. package/src/components/empty-list/index.scss +97 -0
  19. package/src/components/file-input/index.scss +119 -0
  20. package/src/components/file-input/index.vue +333 -0
  21. package/src/components/font-input/index.scss +47 -0
  22. package/src/components/font-input/index.vue +154 -0
  23. package/src/components/form/index.js +228 -0
  24. package/src/components/form/index.scss +398 -0
  25. package/src/components/header-announcement/index.scss +48 -0
  26. package/src/components/input/index.js +85 -0
  27. package/src/components/input/index.scss +87 -0
  28. package/src/components/link/index.scss +266 -0
  29. package/src/components/loader/index.js +65 -0
  30. package/src/components/loader/index.scss +99 -0
  31. package/src/components/nobr/index.scss +5 -0
  32. package/src/components/overlay/index.js +82 -0
  33. package/src/components/overlay/index.scss +29 -0
  34. package/src/components/pagination/index.scss +59 -0
  35. package/src/components/pagination-group/index.js +5 -0
  36. package/src/components/pagination-group/index.scss +86 -0
  37. package/src/components/quantity-input/index.scss +179 -0
  38. package/src/components/quantity-input/index.vue +141 -0
  39. package/src/components/radio/index.scss +80 -0
  40. package/src/components/radio-list/index.scss +79 -0
  41. package/src/components/range-input/index.scss +109 -0
  42. package/src/components/range-input/index.vue +185 -0
  43. package/src/components/rating-input/index.js +32 -0
  44. package/src/components/rating-input/index.scss +36 -0
  45. package/src/components/select/index.js +6 -0
  46. package/src/components/select/index.scss +84 -0
  47. package/src/components/sidebar/index.js +174 -0
  48. package/src/components/sidebar/index.scss +398 -0
  49. package/src/components/simple-page/index.scss +32 -0
  50. package/src/components/slider-nav/index.scss +82 -0
  51. package/src/components/slider-pagination/index.scss +130 -0
  52. package/src/components/spinner/index.scss +35 -0
  53. package/src/components/sr-only/index.scss +25 -0
  54. package/src/components/steps/index.scss +155 -0
  55. package/src/components/suggest-input/index.scss +141 -0
  56. package/src/components/suggest-input/index.vue +324 -0
  57. package/src/components/tabs/index.js +192 -0
  58. package/src/components/tabs/index.scss +137 -0
  59. package/src/components/time-select/index.scss +27 -0
  60. package/src/components/toggle/index.scss +76 -0
  61. package/src/components/tooltip/index.js +69 -0
  62. package/src/components/tooltip/index.scss +112 -0
  63. package/src/components/total/index.scss +162 -0
  64. package/src/components/warning-message/index.scss +170 -0
  65. package/src/components/warning-message/index.vue +94 -0
  66. package/src/components/wysiwyg/index.scss +344 -0
  67. package/src/helpers/css-var.js +8 -0
  68. package/src/helpers/error-handler.js +61 -0
  69. package/src/helpers/get-smart-captcha.js +7 -0
  70. package/src/helpers/is-in-viewport.js +10 -0
  71. package/src/helpers/mm.js +52 -0
  72. package/src/helpers/scroll-to.js +21 -0
  73. package/src/variables.scss +1295 -0
@@ -0,0 +1,155 @@
1
+ @use '../../variables' as *;
2
+
3
+ $b: '.steps';
4
+
5
+ #{$b} {
6
+ overflow: hidden;
7
+ position: relative;
8
+
9
+ &::before {
10
+ background-image: linear-gradient(-90deg, rgba($content-bg, 0%), $content-bg);
11
+ content: none;
12
+ height: 100%;
13
+ left: 0;
14
+ position: absolute;
15
+ top: 0;
16
+ width: 4px * 8;
17
+ z-index: 1;
18
+
19
+ .content_fill_grey &,
20
+ .content__block_fill_grey & {
21
+ background-image: linear-gradient(-90deg, rgba($content-bg-grey, 0%), $content-bg-grey);
22
+ }
23
+ }
24
+
25
+ &::after {
26
+ background-image: linear-gradient(90deg, rgba($content-bg, 0%), $content-bg);
27
+ content: '';
28
+ height: 100%;
29
+ position: absolute;
30
+ right: 0;
31
+ top: 0;
32
+ width: 4px * 8;
33
+ z-index: 1;
34
+
35
+ @media (--tablet-landscape-up) {
36
+ content: none;
37
+ }
38
+
39
+ .content_fill_grey &,
40
+ .content__block_fill_grey & {
41
+ background-image: linear-gradient(90deg, rgba($content-bg-grey, 0%), $content-bg-grey);
42
+ }
43
+ }
44
+
45
+ &_1 {
46
+ }
47
+
48
+ &_2 {
49
+ &::before {
50
+ content: '';
51
+
52
+ @media (--tablet-landscape-up) {
53
+ content: none;
54
+ }
55
+ }
56
+ }
57
+
58
+ &_3 {
59
+ &::before {
60
+ content: '';
61
+
62
+ @media (--tablet-landscape-up) {
63
+ content: none;
64
+ }
65
+ }
66
+ }
67
+
68
+ &__list {
69
+ counter-reset: index;
70
+ display: flex;
71
+ position: relative;
72
+
73
+ #{$b}_1 & {
74
+ left: 0;
75
+ }
76
+
77
+ #{$b}_2 & {
78
+ left: -25%;
79
+
80
+ @media (--tablet-landscape-up) {
81
+ left: 0;
82
+ }
83
+ }
84
+
85
+ #{$b}_3 & {
86
+ left: -50%;
87
+
88
+ @media (--tablet-landscape-up) {
89
+ left: 0;
90
+ }
91
+ }
92
+ }
93
+
94
+ &__item {
95
+ align-items: center;
96
+ display: flex;
97
+
98
+ &:not(:first-child) {
99
+ margin-left: 4px * 2;
100
+ }
101
+
102
+ &:last-child {
103
+ &::after {
104
+ content: none;
105
+ }
106
+ }
107
+
108
+ &::before {
109
+ align-items: center;
110
+ background-color: $steps-item-number-bg;
111
+ border: $steps-item-number-border-width solid $steps-item-number-border-color;
112
+ border-radius: 50%;
113
+ color: $steps-item-number-color;
114
+ content: counter(index);
115
+ counter-increment: index;
116
+ display: flex;
117
+ flex-shrink: 0;
118
+ height: 4px * 6;
119
+ justify-content: center;
120
+ width: 4px * 6;
121
+ }
122
+
123
+ &::after {
124
+ border-bottom: 2px solid $steps-item-arrow-bg;
125
+ border-right: 2px solid $steps-item-arrow-bg;
126
+ content: '';
127
+ height: 4px * 1.5;
128
+ margin-left: 4px * 1.5;
129
+ transform: rotate(-45deg);
130
+ width: 4px * 1.5;
131
+ }
132
+
133
+ &_active {
134
+ &::before {
135
+ background-color: $steps-item-active-number-bg;
136
+ color: $steps-item-active-number-color;
137
+
138
+ @include dark-border-disable;
139
+ }
140
+
141
+ &::after {
142
+ border-bottom-color: $steps-item-active-arrow-bg;
143
+ border-right-color: $steps-item-active-arrow-bg;
144
+ }
145
+ }
146
+ }
147
+
148
+ &__label {
149
+ color: $steps-label-color;
150
+ margin-left: 4px * 2;
151
+ white-space: nowrap;
152
+
153
+ @include font($steps-label-font);
154
+ }
155
+ }
@@ -0,0 +1,141 @@
1
+ @use 'sass:math';
2
+ @use '../../variables' as *;
3
+
4
+ $b: '.suggest-input';
5
+
6
+ #{$b} {
7
+ position: relative;
8
+
9
+ &__input {
10
+ position: relative;
11
+
12
+ &_active {
13
+ z-index: 11;
14
+ }
15
+ }
16
+
17
+ &__loader {
18
+ border-bottom-right-radius: $input-radius;
19
+ border-top-right-radius: $input-radius;
20
+ container-type: size;
21
+ height: $input-height - $input-border-width * 2;
22
+ padding: math.div($input-height - 4px * 5, 2);
23
+ position: absolute;
24
+ right: $input-border-width;
25
+ top: $input-border-width;
26
+ width: $input-height - $input-border-width * 2;
27
+ z-index: 11;
28
+
29
+ .input_small ~ & {
30
+ height: $input-small-height - $input-border-width * 2;
31
+ padding: math.div($input-small-height - 4px * 4, 2);
32
+ width: $input-small-height - $input-border-width * 2;
33
+ }
34
+ }
35
+
36
+ &__clear {
37
+ align-items: center;
38
+ background-color: $input-bg;
39
+ border-bottom-right-radius: $input-radius;
40
+ border-top-right-radius: $input-radius;
41
+ color: $suggest-input-clear-color;
42
+ cursor: pointer;
43
+ display: flex;
44
+ height: $input-height - $input-border-width * 2;
45
+ justify-content: center;
46
+ position: absolute;
47
+ right: 1px;
48
+ top: 1px;
49
+ transition-duration: var(--duration-micro-s);
50
+ transition-property: color;
51
+ transition-timing-function: var(--easing-standard);
52
+ width: $input-height - $input-border-width * 2;
53
+ z-index: 11;
54
+
55
+ &:hover,
56
+ &:focus-visible {
57
+ color: $suggest-input-clear-active-color;
58
+ }
59
+
60
+ .input_small ~ & {
61
+ height: $input-small-height - $input-border-width * 2;
62
+ width: $input-small-height - $input-border-width * 2;
63
+ }
64
+ }
65
+
66
+ &__clear-icon {
67
+ fill: currentcolor;
68
+ height: 4px * 4;
69
+ width: 4px * 4;
70
+
71
+ .input_small ~ #{$b}__clear & {
72
+ height: 4px * 3;
73
+ width: 4px * 3;
74
+ }
75
+ }
76
+
77
+ &__list {
78
+ background-color: $suggest-input-list-bg;
79
+ border-bottom-left-radius: $suggest-input-list-radius;
80
+ border-bottom-right-radius: $suggest-input-list-radius;
81
+ box-shadow: $suggest-input-list-shadow;
82
+ overflow: auto;
83
+ -webkit-overflow-scrolling: touch;
84
+ position: absolute;
85
+ top: $input-height;
86
+ width: 100%;
87
+ z-index: 10;
88
+
89
+ @include dark-border($width: 1px);
90
+ @include dark-border-disable((top));
91
+
92
+ @if $input-radius != 0 {
93
+ margin-top: -4px * 2;
94
+ max-height: $suggest-input-item-height * 5 + 4px * 2;
95
+ padding-top: 4px * 2;
96
+ } @else {
97
+ max-height: $suggest-input-item-height * 5;
98
+ }
99
+
100
+ .input_small ~ & {
101
+ top: $input-small-height;
102
+ }
103
+ }
104
+
105
+ &__list-item {
106
+ align-items: center;
107
+ color: $suggest-input-item-color;
108
+ cursor: pointer;
109
+ display: flex;
110
+ min-height: $suggest-input-item-height;
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
+
116
+ @include font($suggest-input-item-font);
117
+
118
+ &_focus,
119
+ &:hover {
120
+ background-color: $suggest-input-item-active-bg;
121
+ color: $suggest-input-item-active-color;
122
+ }
123
+
124
+ mark {
125
+ background-color: transparent;
126
+ color: $suggest-input-item-mark-color;
127
+
128
+ @include font($suggest-input-item-mark-font);
129
+ }
130
+ }
131
+
132
+ &__not-found {
133
+ align-items: center;
134
+ color: $suggest-input-not-found-color;
135
+ display: flex;
136
+ min-height: $suggest-input-item-height;
137
+ padding: 4px $input-padding-x;
138
+
139
+ @include font($suggest-input-not-found-font);
140
+ }
141
+ }
@@ -0,0 +1,324 @@
1
+ <template>
2
+ <div ref="$root" class="suggest-input" @keydown.esc="esc()">
3
+ <input
4
+ :id="id"
5
+ ref="$input"
6
+ v-model="model"
7
+ class="suggest-input__input input"
8
+ :class="[
9
+ showSuggests ? 'suggest-input__input_active' : '',
10
+ small ? 'input_small' : '',
11
+ error ? 'input_error' : '',
12
+ ...inputCssClasses,
13
+ ]"
14
+ type="text"
15
+ :name="name"
16
+ :required="required"
17
+ :disabled="disabled"
18
+ :placeholder="placeholder"
19
+ autocomplete="off"
20
+ @input="handleInput()"
21
+ @focusin="focusin()"
22
+ @focusout="focusout($event)"
23
+ @keydown="arrowNavigation($event)"
24
+ />
25
+ <div v-show="isLoading" class="suggest-input__loader"><div class="spinner"></div></div>
26
+ <button v-show="isClearVisible" ref="$clear" class="suggest-input__clear" type="button" @click="clear()">
27
+ <svg class="suggest-input__clear-icon"><use :href="`${ICONS_PATH}#close`" /></svg>
28
+ </button>
29
+ <div v-show="showSuggests" ref="$list" class="suggest-input__list" @mouseover="focusSuggestIndex = -1">
30
+ <template v-if="suggests.length > 0">
31
+ <div
32
+ v-for="(item, index) in formattedSuggests"
33
+ :key="index"
34
+ class="suggest-input__list-item"
35
+ :class="{ 'suggest-input__list-item_focus': index === focusSuggestIndex }"
36
+ tabindex="0"
37
+ @click="select(item)"
38
+ >
39
+ <span v-if="hightlightMatches" v-html="item.formatted_label"></span>
40
+ <span v-else v-html="item.label"></span>
41
+ </div>
42
+ </template>
43
+ <div v-else class="suggest-input__not-found" v-html="notFoundMessage"></div>
44
+ </div>
45
+ <slot name="content"></slot>
46
+ </div>
47
+ </template>
48
+
49
+ <script setup>
50
+ /* globals BX */
51
+
52
+ import { computed, nextTick, onMounted, ref, toRefs, watch } from 'vue';
53
+
54
+ import { errorHandler } from '../../helpers/error-handler.js';
55
+
56
+ const ICONS_PATH = window.ICONS_PATH;
57
+
58
+ const emit = defineEmits(['focusin', 'focusout', 'select']);
59
+
60
+ const model = defineModel({ type: String, default: '' });
61
+
62
+ const props = defineProps({
63
+ id: {
64
+ type: String,
65
+ default: '',
66
+ },
67
+ name: {
68
+ type: String,
69
+ default: '',
70
+ },
71
+ placeholder: {
72
+ type: String,
73
+ default: ' ',
74
+ },
75
+ required: {
76
+ type: Boolean,
77
+ },
78
+ disabled: {
79
+ type: Boolean,
80
+ },
81
+ action: {
82
+ type: String,
83
+ required: true,
84
+ },
85
+ queryName: {
86
+ type: String,
87
+ default: 'query',
88
+ },
89
+ minlength: {
90
+ type: Number,
91
+ default: 3,
92
+ },
93
+ throttle: {
94
+ type: Number,
95
+ default: 1000,
96
+ },
97
+ inputCssClasses: {
98
+ type: Array,
99
+ default() {
100
+ return [];
101
+ },
102
+ },
103
+ hightlightMatches: {
104
+ type: Boolean,
105
+ },
106
+ notFoundMessage: {
107
+ type: String,
108
+ default: 'По&nbsp;вашему запросу ничего не&nbsp;найдено',
109
+ },
110
+ small: {
111
+ type: Boolean,
112
+ },
113
+ error: {
114
+ type: Boolean,
115
+ },
116
+ });
117
+
118
+ const {
119
+ id,
120
+ name,
121
+ placeholder,
122
+ required,
123
+ disabled,
124
+ action,
125
+ queryName,
126
+ minlength,
127
+ throttle,
128
+ inputCssClasses,
129
+ hightlightMatches,
130
+ small,
131
+ notFoundMessage,
132
+ } = toRefs(props);
133
+
134
+ const $root = ref();
135
+ const $input = ref();
136
+ const $list = ref();
137
+ const $clear = ref();
138
+
139
+ const suggests = ref([]);
140
+ const formattedSuggests = ref([]);
141
+ const focusSuggestIndex = ref(-1);
142
+
143
+ watch(suggests, () => {
144
+ focusSuggestIndex.value = -1;
145
+ const regexp = new RegExp(model.value, 'i');
146
+ formattedSuggests.value = suggests.value.map((item) => {
147
+ if (hightlightMatches.value) {
148
+ const match = regexp.exec(item.label);
149
+ item.formatted_label = match ? item.label.replace(match[0], `<mark>${match[0]}</mark>`) : item.label;
150
+ }
151
+ return item;
152
+ });
153
+ });
154
+
155
+ const showSuggests = ref(false);
156
+ const itemHeight = ref(0);
157
+
158
+ watch(showSuggests, async (value) => {
159
+ if (value) {
160
+ await nextTick();
161
+ itemHeight.value = $list.value.querySelector('.suggest-input__list-item').offsetHeight;
162
+ } else {
163
+ suggests.value = [];
164
+ await nextTick();
165
+ itemHeight.value = 0;
166
+ }
167
+ });
168
+
169
+ const maxItemsWithoutScroll = computed(() => {
170
+ if (itemHeight.value) {
171
+ return Math.floor($list.value.offsetHeight / itemHeight.value);
172
+ }
173
+ return 0;
174
+ });
175
+
176
+ const indexesToScroll = computed(() => {
177
+ if (maxItemsWithoutScroll.value) {
178
+ const indexes = [0];
179
+ for (const index of suggests.value.keys()) {
180
+ const nextIndex = indexes.at(-1) + maxItemsWithoutScroll.value;
181
+ if (nextIndex > suggests.value.length) {
182
+ break;
183
+ }
184
+ if (index === nextIndex) {
185
+ indexes.push(index);
186
+ }
187
+ }
188
+ return indexes;
189
+ }
190
+ return [];
191
+ });
192
+
193
+ const isLoading = ref(false);
194
+ const isClearVisible = computed(() => model.value.length >= minlength.value && !isLoading.value);
195
+
196
+ let throttleTimeout;
197
+
198
+ const handleInput = () => {
199
+ clearTimeout(throttleTimeout);
200
+ if (model.value.length >= minlength.value) {
201
+ throttleTimeout = setTimeout(async () => {
202
+ isLoading.value = true;
203
+ const url = new URL(action.value, location.origin);
204
+ url.searchParams.append(queryName.value, model.value);
205
+ try {
206
+ const response = await fetch(url, {
207
+ headers: {
208
+ 'X-Requested-With': 'XMLHttpRequest',
209
+ 'X-Session-Id': BX.message('bitrix_sessid'),
210
+ },
211
+ });
212
+ if (!response.ok) {
213
+ throw response;
214
+ }
215
+ const data = await response.json();
216
+ isLoading.value = false;
217
+ suggests.value = data;
218
+ showSuggests.value = true;
219
+ } catch (error) {
220
+ if (error.status === 401) {
221
+ location.reload();
222
+ } else {
223
+ isLoading.value = false;
224
+ errorHandler(error);
225
+ }
226
+ }
227
+ }, throttle.value);
228
+ } else {
229
+ showSuggests.value = false;
230
+ }
231
+ };
232
+
233
+ const arrowNavigation = (event) => {
234
+ if (showSuggests.value) {
235
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
236
+ const selectionStart = $input.value.selectionStart;
237
+ const selectionEnd = $input.value.selectionEnd;
238
+ setTimeout(() => {
239
+ $input.value.setSelectionRange(selectionStart, selectionEnd);
240
+ });
241
+ switch (event.key) {
242
+ case 'ArrowDown': {
243
+ focusSuggestIndex.value =
244
+ suggests.value.length === focusSuggestIndex.value + 1 ? 0 : focusSuggestIndex.value + 1;
245
+ if (
246
+ focusSuggestIndex.value !== 0 &&
247
+ focusSuggestIndex.value !== suggests.value.length - 1 &&
248
+ indexesToScroll.value.includes(focusSuggestIndex.value)
249
+ ) {
250
+ $list.value.scrollTop += maxItemsWithoutScroll.value * itemHeight.value;
251
+ } else if (focusSuggestIndex.value === 0) {
252
+ $list.value.scrollTop = 0;
253
+ }
254
+ break;
255
+ }
256
+ case 'ArrowUp': {
257
+ focusSuggestIndex.value =
258
+ focusSuggestIndex.value <= 0 ? suggests.value.length - 1 : focusSuggestIndex.value - 1;
259
+ if (
260
+ focusSuggestIndex.value !== 0 &&
261
+ focusSuggestIndex.value !== suggests.value.length - 1 &&
262
+ indexesToScroll.value.includes(focusSuggestIndex.value + 1)
263
+ ) {
264
+ $list.value.scrollTop -= maxItemsWithoutScroll.value * itemHeight.value;
265
+ } else if (focusSuggestIndex.value === suggests.value.length - 1) {
266
+ $list.value.scrollTop = suggests.value.length * itemHeight.value;
267
+ }
268
+ break;
269
+ }
270
+ }
271
+ }
272
+ if (event.key === 'Enter' && focusSuggestIndex.value >= 0) {
273
+ // Ждём, чтобы событие нажатия Enter сработало у других обработчиков (например, в формах)
274
+ setTimeout(() => {
275
+ select(suggests.value[focusSuggestIndex.value]);
276
+ });
277
+ }
278
+ }
279
+ };
280
+
281
+ const focusin = async () => {
282
+ emit('focusin');
283
+ await nextTick();
284
+ handleInput();
285
+ };
286
+
287
+ const focusout = async ({ relatedTarget }) => {
288
+ if (!relatedTarget || (!$list.value.contains(relatedTarget) && !$clear.value.contains(relatedTarget))) {
289
+ showSuggests.value = false;
290
+ }
291
+ await nextTick();
292
+ emit('focusout');
293
+ };
294
+
295
+ const esc = () => {
296
+ // Ждём, чтобы событие нажатия Esc сработало у других обработчиков (например, в диалоговых окнах)
297
+ setTimeout(() => {
298
+ showSuggests.value = false;
299
+ });
300
+ };
301
+
302
+ const clear = () => {
303
+ clearTimeout(throttleTimeout);
304
+ model.value = '';
305
+ showSuggests.value = false;
306
+ };
307
+
308
+ const select = ({ label, value }) => {
309
+ model.value = label;
310
+ showSuggests.value = false;
311
+ emit('select', {
312
+ input: model.value,
313
+ value,
314
+ });
315
+ };
316
+
317
+ onMounted(() => {
318
+ $root.value.closest('.form')?.addEventListener('keydown', (event) => {
319
+ if (event.key === 'Enter' && focusSuggestIndex.value >= 0) {
320
+ event.preventDefault();
321
+ }
322
+ });
323
+ });
324
+ </script>