itube-specs 0.0.902 → 0.0.910
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/assets/icons/common/flag.svg +1 -1
- package/assets/icons/common/heart-stroke.svg +1 -1
- package/assets/icons/common/heart.svg +1 -1
- package/assets/icons/common/share.svg +1 -1
- package/assets/icons/common/thumbs-down-filled.svg +1 -0
- package/assets/icons/common/thumbs-down.svg +1 -1
- package/assets/icons/common/thumbs-up-filled.svg +1 -0
- package/assets/icons/common/thumbs-up.svg +1 -1
- package/assets/scss/themes/dark/_theme.scss +49 -19
- package/assets/scss/themes/light/_theme.scss +46 -16
- package/assets/scss/vars/_palette.scss +5 -0
- package/assets/scss/vars/_vars.scss +1 -0
- package/components/auth/login.vue +2 -2
- package/components/auth/recovery.vue +2 -2
- package/components/auth/register.vue +2 -2
- package/components/card/collections-more.vue +133 -0
- package/components/card/playlist.vue +6 -1
- package/components/grid/playlists.vue +3 -0
- package/components/info/main.vue +18 -14
- package/components/library/feed.vue +20 -39
- package/components/library/follows.vue +19 -4
- package/components/player/interface.vue +132 -58
- package/components/player/like.vue +82 -60
- package/components/playlist/add.vue +2 -2
- package/components/playlist/delete-video.vue +2 -2
- package/components/playlist/edit.vue +3 -3
- package/components/profile/delete.vue +2 -2
- package/components/profile/setup.vue +2 -2
- package/components/report/main.vue +9 -4
- package/components/search/input.vue +9 -9
- package/components/section/collections.vue +110 -0
- package/components/ui/follow-btn.vue +162 -0
- package/components/ui/section-title.vue +2 -0
- package/components/ui/snackbar.vue +93 -44
- package/composables/__tests__/use-snackbar.test.ts +126 -20
- package/composables/use-snackbar.ts +75 -20
- package/composables/use-user.ts +4 -4
- package/package.json +1 -1
- package/pages/contacts.vue +2 -2
- package/pages/library/index.vue +1 -1
- package/pages/playlists/index.vue +55 -4
- package/assets/icons/common/crest-in-circle.svg +0 -1
|
@@ -28,7 +28,7 @@ import { AuthorizationApiService } from '../../services/api/authorization.servic
|
|
|
28
28
|
|
|
29
29
|
const { t } = useI18n();
|
|
30
30
|
const { signOut } = useUser(AuthorizationApiService);
|
|
31
|
-
const {
|
|
31
|
+
const { show } = useSnackbar();
|
|
32
32
|
|
|
33
33
|
const loading = ref(false);
|
|
34
34
|
const isConfirmOpen = ref(false);
|
|
@@ -38,7 +38,7 @@ async function deleteProfile() {
|
|
|
38
38
|
loading.value = true;
|
|
39
39
|
await AuthorizationApiService.deleteProfile();
|
|
40
40
|
isConfirmOpen.value = false;
|
|
41
|
-
|
|
41
|
+
show('profile_deleted');
|
|
42
42
|
signOut();
|
|
43
43
|
} catch (err) {
|
|
44
44
|
console.log(err, 'error delete profile');
|
|
@@ -187,7 +187,7 @@ function setDate(string: string) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
const deletingAvatar = ref(false);
|
|
190
|
-
const {
|
|
190
|
+
const { show } = useSnackbar();
|
|
191
191
|
|
|
192
192
|
async function deleteAvatar() {
|
|
193
193
|
deletingAvatar.value = true;
|
|
@@ -196,7 +196,7 @@ async function deleteAvatar() {
|
|
|
196
196
|
form.value.avatar = '';
|
|
197
197
|
await getProfile(true);
|
|
198
198
|
avatarPreview.value = '';
|
|
199
|
-
|
|
199
|
+
show('avatar_deleted');
|
|
200
200
|
} finally {
|
|
201
201
|
deletingAvatar.value = false;
|
|
202
202
|
}
|
|
@@ -147,8 +147,9 @@
|
|
|
147
147
|
|
|
148
148
|
<script setup lang="ts">
|
|
149
149
|
import { reportFormsScheme } from '../../lib';
|
|
150
|
-
import { ReportFormsSubjects, validateEmail, validatePhone } from '../../runtime';
|
|
150
|
+
import { checkDeviceWidth, ReportFormsSubjects, validateEmail, validatePhone } from '../../runtime';
|
|
151
151
|
import type { InputTypes, IReportForm, IReportRequest } from '../../types';
|
|
152
|
+
import { CSS_BREAKPOINTS } from '../../config/css-breakpoints';
|
|
152
153
|
|
|
153
154
|
const { initRecaptcha, getRecaptchaToken } = useRecaptcha();
|
|
154
155
|
|
|
@@ -180,7 +181,11 @@ const {
|
|
|
180
181
|
onBack,
|
|
181
182
|
} = useReportPopup();
|
|
182
183
|
|
|
183
|
-
const
|
|
184
|
+
const { isTablet } = checkDeviceWidth(CSS_BREAKPOINTS);
|
|
185
|
+
|
|
186
|
+
/* Возврат в меню карточки имеет смысл только там, где оно само — шит (до md);
|
|
187
|
+
на десктопе стрелка остаётся только для перехода со второго шага на список причин */
|
|
188
|
+
const showBack = computed(() => activeStep.value !== 'list' || (!!onBack.value && isTablet.value));
|
|
184
189
|
|
|
185
190
|
function handleBack() {
|
|
186
191
|
const callback = onBack.value;
|
|
@@ -243,7 +248,7 @@ function onFieldEnter(value: string) {
|
|
|
243
248
|
else inputRefs.value[ list[ index + 1 ] ]?.focus();
|
|
244
249
|
}
|
|
245
250
|
|
|
246
|
-
const {
|
|
251
|
+
const { show, showError, resetSnackbar } = useSnackbar();
|
|
247
252
|
|
|
248
253
|
function resetForm() {
|
|
249
254
|
Object.keys(form.value).forEach((key) => {
|
|
@@ -320,7 +325,7 @@ async function submit() {
|
|
|
320
325
|
});
|
|
321
326
|
closeReportPopup();
|
|
322
327
|
resetForm();
|
|
323
|
-
|
|
328
|
+
show('email_send');
|
|
324
329
|
} catch (error) {
|
|
325
330
|
console.log('error send email', error);
|
|
326
331
|
showError('email_error');
|
|
@@ -97,7 +97,7 @@ $icon-size: 16px;
|
|
|
97
97
|
.search-input {
|
|
98
98
|
position: relative;
|
|
99
99
|
display: block;
|
|
100
|
-
border-radius: $radius;
|
|
100
|
+
border-radius: $radius-12;
|
|
101
101
|
|
|
102
102
|
@include to-br(sm) {
|
|
103
103
|
background-color: $search-bg--mobile;
|
|
@@ -144,15 +144,15 @@ $icon-size: 16px;
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
.search-field__input::placeholder {
|
|
147
|
-
@include
|
|
147
|
+
@include to-br(sm) {
|
|
148
|
+
color: $search-placeholder--mobile;
|
|
149
|
+
}
|
|
148
150
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
151
|
+
@include from-br(sm) {
|
|
152
|
+
@include font-sm;
|
|
152
153
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
154
|
+
color: $search-placeholder;
|
|
155
|
+
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
.search-field__search {
|
|
@@ -165,7 +165,7 @@ $icon-size: 16px;
|
|
|
165
165
|
color: $input-text;
|
|
166
166
|
padding: 10px;
|
|
167
167
|
background-color: $search-button-bg;
|
|
168
|
-
border-radius: $radius
|
|
168
|
+
border-radius: $radius-md;
|
|
169
169
|
|
|
170
170
|
@include from-br(sm) {
|
|
171
171
|
cursor: pointer;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!-- Блок редакционных подборок: заголовок с бейджем, ссылка «все» и сетка карточек + плитка «ещё» -->
|
|
2
|
+
<template>
|
|
3
|
+
<section
|
|
4
|
+
v-if="items.length > 0"
|
|
5
|
+
class="section-collections"
|
|
6
|
+
>
|
|
7
|
+
<header class="section-collections__header">
|
|
8
|
+
<UiSectionTitle
|
|
9
|
+
:title="t('collections_by')"
|
|
10
|
+
title-tag="h2"
|
|
11
|
+
size="xl"
|
|
12
|
+
>
|
|
13
|
+
<span class="section-collections__badge">{{ t('editorial') }}</span>
|
|
14
|
+
</UiSectionTitle>
|
|
15
|
+
|
|
16
|
+
<UiLink
|
|
17
|
+
:to="generateLink(COLLECTIONS_LINK)"
|
|
18
|
+
theme="secondary"
|
|
19
|
+
class="section-collections__link"
|
|
20
|
+
>
|
|
21
|
+
{{ t('following.view_all') }}
|
|
22
|
+
<UiIcon
|
|
23
|
+
name="arrow-right"
|
|
24
|
+
size="16"
|
|
25
|
+
/>
|
|
26
|
+
</UiLink>
|
|
27
|
+
</header>
|
|
28
|
+
|
|
29
|
+
<GridPlaylists
|
|
30
|
+
:items="items"
|
|
31
|
+
no-actions
|
|
32
|
+
>
|
|
33
|
+
<CardCollectionsMore
|
|
34
|
+
:to="COLLECTIONS_LINK"
|
|
35
|
+
:text="t('show_more_collections')"
|
|
36
|
+
:thumbs="restThumbs"
|
|
37
|
+
:count="restCount"
|
|
38
|
+
/>
|
|
39
|
+
</GridPlaylists>
|
|
40
|
+
</section>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script setup lang="ts">
|
|
44
|
+
import type { IPlaylistCard, IThumbUrls } from '../../types';
|
|
45
|
+
|
|
46
|
+
const props = withDefaults(defineProps<{
|
|
47
|
+
dataItems: IPlaylistCard[]
|
|
48
|
+
/** Сколько карточек показать до плитки «ещё» */
|
|
49
|
+
limit?: number
|
|
50
|
+
}>(), {
|
|
51
|
+
limit: 3,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const COLLECTIONS_LINK = '/playlists/collections';
|
|
55
|
+
|
|
56
|
+
/** Сетка 4×4 в плитке «ещё» */
|
|
57
|
+
const THUMBS_LIMIT = 16;
|
|
58
|
+
|
|
59
|
+
const { t } = useI18n();
|
|
60
|
+
|
|
61
|
+
const { generateLink } = useGenerateLink();
|
|
62
|
+
|
|
63
|
+
const items = computed(() => (props.dataItems || []).slice(0, props.limit));
|
|
64
|
+
|
|
65
|
+
const rest = computed(() => (props.dataItems || []).slice(props.limit));
|
|
66
|
+
|
|
67
|
+
const restCount = computed(() => rest.value.length);
|
|
68
|
+
|
|
69
|
+
/** Обложки для плитки «ещё»: сначала по первой от каждой скрытой подборки, потом по вторым и так далее */
|
|
70
|
+
const restThumbs = computed(() => {
|
|
71
|
+
const byIndex = (index: number) => rest.value
|
|
72
|
+
.map(item => item.thumbUrls?.[index])
|
|
73
|
+
.filter((thumb): thumb is IThumbUrls => Boolean(thumb));
|
|
74
|
+
|
|
75
|
+
return [0, 1, 2, 3].flatMap(index => byIndex(index)).slice(0, THUMBS_LIMIT);
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style lang="scss">
|
|
80
|
+
.section-collections {
|
|
81
|
+
margin-bottom: 40px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.section-collections__header {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: space-between;
|
|
88
|
+
column-gap: 16px;
|
|
89
|
+
margin-bottom: 24px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.section-collections__badge {
|
|
93
|
+
@include font-3xs;
|
|
94
|
+
|
|
95
|
+
display: inline-flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
vertical-align: middle;
|
|
98
|
+
padding-block: 2px;
|
|
99
|
+
padding-inline: 8px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
color: $collections-badge-text;
|
|
102
|
+
background-color: $collections-badge-bg;
|
|
103
|
+
border: 1px solid $collections-badge-border;
|
|
104
|
+
border-radius: $radius-full;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.section-collections__link {
|
|
108
|
+
flex-shrink: 0;
|
|
109
|
+
}
|
|
110
|
+
</style>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<!-- Кнопка подписки: «Follow» → «Following», при наведении на подписку становится красной «Unfollow». На мобиле только иконка. Отписка сама показывает снекбар с undo -->
|
|
2
|
+
<template>
|
|
3
|
+
<button
|
|
4
|
+
:aria-label="ariaLabel"
|
|
5
|
+
:class="[`--${size}`, { '--following': following }]"
|
|
6
|
+
class="ui-follow-btn"
|
|
7
|
+
type="button"
|
|
8
|
+
@click="onClick"
|
|
9
|
+
>
|
|
10
|
+
<UiIcon
|
|
11
|
+
:name="following ? 'check' : 'plus'"
|
|
12
|
+
class="ui-follow-btn__icon"
|
|
13
|
+
size="16"
|
|
14
|
+
/>
|
|
15
|
+
<span class="ui-follow-btn__label">{{ following ? t('library.following_state') : t('follow') }}</span>
|
|
16
|
+
|
|
17
|
+
<template v-if="following">
|
|
18
|
+
<UiIcon
|
|
19
|
+
class="ui-follow-btn__icon-hover"
|
|
20
|
+
name="minus"
|
|
21
|
+
size="16"
|
|
22
|
+
/>
|
|
23
|
+
<span class="ui-follow-btn__label-hover">{{ t('unfollow') }}</span>
|
|
24
|
+
</template>
|
|
25
|
+
</button>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
const props = withDefaults(
|
|
30
|
+
defineProps<{
|
|
31
|
+
/** Имя модели / канала / категории — уходит в aria-label, чтобы кнопки в списке различались на слух */
|
|
32
|
+
name: string
|
|
33
|
+
following?: boolean
|
|
34
|
+
/** `s` — под размеры `UiBtn size="s"` в шапках карточек */
|
|
35
|
+
size?: 'm' | 's'
|
|
36
|
+
}>(),
|
|
37
|
+
{
|
|
38
|
+
size: 'm',
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const emit = defineEmits<{
|
|
43
|
+
(eventName: 'toggle'): void,
|
|
44
|
+
}>();
|
|
45
|
+
|
|
46
|
+
const { t } = useI18n();
|
|
47
|
+
const { show } = useSnackbar();
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Снекбар с undo вшит в кнопку: отменять хочется только отписку, и текст у неё всегда
|
|
51
|
+
* один и тот же — раскладывать эту механику по каждому вызову смысла нет.
|
|
52
|
+
* Само состояние остаётся у родителя, undo просто переключает подписку обратно.
|
|
53
|
+
*/
|
|
54
|
+
function onClick() {
|
|
55
|
+
const wasFollowing = props.following;
|
|
56
|
+
|
|
57
|
+
emit('toggle');
|
|
58
|
+
|
|
59
|
+
if (!wasFollowing) return;
|
|
60
|
+
|
|
61
|
+
show('unfollowed', {
|
|
62
|
+
icon: 'minus',
|
|
63
|
+
highlight: props.name,
|
|
64
|
+
onAction: () => emit('toggle'),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Доступное имя повторяет видимую подпись (Following / Follow), а не действие: иначе
|
|
70
|
+
* ломается WCAG «Label in Name», ведь на мобиле подписи нет вообще.
|
|
71
|
+
*/
|
|
72
|
+
const ariaLabel = computed(() => props.following
|
|
73
|
+
? t('following_aria', { name: props.name })
|
|
74
|
+
: t('follow_aria', { name: props.name }));
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<style lang="scss">
|
|
78
|
+
.ui-follow-btn {
|
|
79
|
+
@include font-sm;
|
|
80
|
+
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
column-gap: 6px;
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
width: 36px;
|
|
87
|
+
height: 36px;
|
|
88
|
+
font-weight: 500;
|
|
89
|
+
border-radius: $radius-md;
|
|
90
|
+
background-color: $follow-btn-bg;
|
|
91
|
+
border: 1px solid $follow-btn-border;
|
|
92
|
+
color: $follow-btn-text;
|
|
93
|
+
|
|
94
|
+
@include from-br(sm) {
|
|
95
|
+
width: 124px;
|
|
96
|
+
height: auto;
|
|
97
|
+
padding: 8px 16px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@include hover(background-color, border-color) {
|
|
101
|
+
background-color: $follow-btn-bg--hover;
|
|
102
|
+
border-color: $follow-btn-border--hover;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Размер под соседние `UiBtn size="s"`: без фиксированной ширины — в шапке кнопки жмутся по контенту */
|
|
107
|
+
.ui-follow-btn.--s {
|
|
108
|
+
width: 32px;
|
|
109
|
+
height: 32px;
|
|
110
|
+
border-radius: $radius-lg;
|
|
111
|
+
|
|
112
|
+
@include from-br(sm) {
|
|
113
|
+
width: max-content;
|
|
114
|
+
height: auto;
|
|
115
|
+
min-height: 32px;
|
|
116
|
+
padding: 6px 12px;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ui-follow-btn.--following {
|
|
121
|
+
background-color: $follow-btn-following-bg;
|
|
122
|
+
border-color: $follow-btn-following-border;
|
|
123
|
+
color: $follow-btn-following-text;
|
|
124
|
+
|
|
125
|
+
@include hover(background-color, border-color, color) {
|
|
126
|
+
background-color: $follow-btn-following-bg--hover;
|
|
127
|
+
border-color: $follow-btn-following-border--hover;
|
|
128
|
+
color: $follow-btn-following-text--hover;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ui-follow-btn__label {
|
|
133
|
+
display: none;
|
|
134
|
+
|
|
135
|
+
@include from-br(sm) {
|
|
136
|
+
display: block;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Подмена подписи и иконки — чистым CSS: на тач-устройствах ховера нет, а JS-стейт там «залипал» бы после тапа */
|
|
141
|
+
.ui-follow-btn__icon-hover,
|
|
142
|
+
.ui-follow-btn__label-hover {
|
|
143
|
+
display: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@media (hover: hover) {
|
|
147
|
+
.ui-follow-btn.--following:is(:hover, :focus-visible) .ui-follow-btn__icon,
|
|
148
|
+
.ui-follow-btn.--following:is(:hover, :focus-visible) .ui-follow-btn__label {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ui-follow-btn.--following:is(:hover, :focus-visible) .ui-follow-btn__icon-hover {
|
|
153
|
+
display: block;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@include from-br(sm) {
|
|
157
|
+
.ui-follow-btn.--following:is(:hover, :focus-visible) .ui-follow-btn__label-hover {
|
|
158
|
+
display: block;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
</style>
|
|
@@ -1,33 +1,68 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
|
|
3
|
+
aria-live="polite"
|
|
4
4
|
class="ui-snackbar"
|
|
5
|
+
role="status"
|
|
6
|
+
@mouseenter="pauseDismiss"
|
|
7
|
+
@mouseleave="resumeDismiss"
|
|
8
|
+
@focusin="pauseDismiss"
|
|
9
|
+
@focusout="resumeDismiss"
|
|
5
10
|
>
|
|
6
|
-
<
|
|
11
|
+
<span class="ui-snackbar__icon-wrapper">
|
|
7
12
|
<UiIcon
|
|
8
13
|
:name="snackbarIcon"
|
|
9
14
|
class="ui-snackbar__icon"
|
|
10
15
|
size="16"
|
|
11
16
|
/>
|
|
12
|
-
</
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
</span>
|
|
18
|
+
<p class="ui-snackbar__text">
|
|
19
|
+
<span class="ui-snackbar__message">{{ message }}</span>
|
|
20
|
+
<strong
|
|
21
|
+
v-if="snackbarHighlight"
|
|
22
|
+
class="ui-snackbar__highlight"
|
|
23
|
+
>{{ snackbarHighlight }}</strong>
|
|
24
|
+
</p>
|
|
25
|
+
<button
|
|
26
|
+
v-if="snackbarActionText"
|
|
27
|
+
class="ui-snackbar__action"
|
|
28
|
+
type="button"
|
|
29
|
+
@click="runSnackbarAction"
|
|
30
|
+
>
|
|
31
|
+
{{ actionLabel }}
|
|
32
|
+
</button>
|
|
18
33
|
</div>
|
|
19
34
|
</template>
|
|
20
35
|
|
|
21
36
|
<script setup lang="ts">
|
|
22
37
|
import { convertString } from '../../runtime';
|
|
23
38
|
|
|
24
|
-
const {
|
|
39
|
+
const {
|
|
40
|
+
snackbarText,
|
|
41
|
+
snackbarIcon,
|
|
42
|
+
snackbarHighlight,
|
|
43
|
+
snackbarActionText,
|
|
44
|
+
runSnackbarAction,
|
|
45
|
+
pauseDismiss,
|
|
46
|
+
resumeDismiss,
|
|
47
|
+
} = useSnackbar();
|
|
25
48
|
|
|
26
49
|
const { t, te } = useI18n();
|
|
27
50
|
|
|
28
51
|
const convertSnackKey = computed(() =>
|
|
29
52
|
convertString().toSnakeCase(snackbarText.value)
|
|
30
53
|
);
|
|
54
|
+
|
|
55
|
+
const message = computed(() =>
|
|
56
|
+
te(`snackbar.${convertSnackKey.value}`)
|
|
57
|
+
? t(`snackbar.${convertSnackKey.value}`)
|
|
58
|
+
: t('snackbar.something_went_wrong')
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const actionLabel = computed(() =>
|
|
62
|
+
te(`snackbar.${snackbarActionText.value}`)
|
|
63
|
+
? t(`snackbar.${snackbarActionText.value}`)
|
|
64
|
+
: t('snackbar.undo')
|
|
65
|
+
);
|
|
31
66
|
</script>
|
|
32
67
|
|
|
33
68
|
<style lang="scss">
|
|
@@ -36,36 +71,38 @@ const convertSnackKey = computed(() =>
|
|
|
36
71
|
|
|
37
72
|
position: fixed;
|
|
38
73
|
z-index: $z-index-popup;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
min-width: 100px;
|
|
43
|
-
width: max-content;
|
|
44
|
-
max-width: 90vw;
|
|
74
|
+
bottom: 16px;
|
|
75
|
+
left: 16px;
|
|
76
|
+
right: 16px;
|
|
45
77
|
display: grid;
|
|
46
|
-
grid-template-columns: auto 1fr auto;
|
|
78
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
47
79
|
align-items: center;
|
|
48
80
|
column-gap: 12px;
|
|
49
81
|
padding: 12px 16px;
|
|
50
|
-
border
|
|
82
|
+
border: 1px solid $snackbar-border;
|
|
83
|
+
border-radius: $radius-md;
|
|
84
|
+
background-color: $snackbar-bg;
|
|
51
85
|
color: $snackbar-text;
|
|
86
|
+
box-shadow: $snackbar-shadow;
|
|
52
87
|
|
|
53
88
|
@include from-br(sm) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
left: 0;
|
|
90
|
+
right: 0;
|
|
91
|
+
width: max-content;
|
|
92
|
+
min-width: 300px;
|
|
93
|
+
max-width: 448px;
|
|
94
|
+
margin-inline: auto;
|
|
58
95
|
}
|
|
59
|
-
}
|
|
60
96
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
97
|
+
&.v-enter-active,
|
|
98
|
+
&.v-leave-active {
|
|
99
|
+
transition: opacity 300ms ease, transform 300ms ease;
|
|
100
|
+
}
|
|
65
101
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
102
|
+
&.v-enter-from,
|
|
103
|
+
&.v-leave-to {
|
|
104
|
+
opacity: 0;
|
|
105
|
+
transform: translateY(16px);
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
108
|
|
|
@@ -75,31 +112,43 @@ const convertSnackKey = computed(() =>
|
|
|
75
112
|
width: 32px;
|
|
76
113
|
height: 32px;
|
|
77
114
|
border-radius: $radius-full;
|
|
115
|
+
background-color: $snackbar-icon-bg;
|
|
78
116
|
}
|
|
79
117
|
|
|
80
|
-
.ui-
|
|
81
|
-
|
|
82
|
-
color: $snackbar-error;
|
|
118
|
+
.ui-snackbar__icon {
|
|
119
|
+
color: $snackbar-icon;
|
|
83
120
|
}
|
|
84
121
|
|
|
85
|
-
.ui-
|
|
86
|
-
|
|
122
|
+
.ui-snackbar__text {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
column-gap: 4px;
|
|
126
|
+
min-width: 0;
|
|
87
127
|
}
|
|
88
128
|
|
|
89
|
-
.ui-
|
|
90
|
-
|
|
129
|
+
.ui-snackbar__message {
|
|
130
|
+
flex-shrink: 0;
|
|
91
131
|
}
|
|
92
132
|
|
|
93
|
-
.ui-
|
|
94
|
-
|
|
95
|
-
|
|
133
|
+
.ui-snackbar__highlight {
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
white-space: nowrap;
|
|
136
|
+
text-overflow: ellipsis;
|
|
137
|
+
font-weight: 500;
|
|
138
|
+
color: $snackbar-highlight;
|
|
96
139
|
}
|
|
97
140
|
|
|
98
|
-
.ui-
|
|
99
|
-
|
|
100
|
-
}
|
|
141
|
+
.ui-snackbar__action {
|
|
142
|
+
@include font-sm;
|
|
101
143
|
|
|
102
|
-
|
|
103
|
-
|
|
144
|
+
padding: 6px 12px;
|
|
145
|
+
border-radius: $radius-md;
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
background-color: $snackbar-action-bg;
|
|
148
|
+
color: $snackbar-action-text;
|
|
149
|
+
|
|
150
|
+
@include hover(background-color) {
|
|
151
|
+
background-color: $snackbar-action-bg--hover;
|
|
152
|
+
}
|
|
104
153
|
}
|
|
105
154
|
</style>
|