itube-specs 0.0.911 → 0.0.913
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/scss/base/_animations.scss +11 -10
- package/assets/scss/mixins/_font-sizes.scss +5 -2
- package/assets/scss/themes/dark/_theme.scss +22 -18
- package/assets/scss/themes/light/_theme.scss +17 -13
- package/assets/scss/vars/_tokens.scss +1 -1
- package/assets/scss/vars/_vars.scss +1 -0
- package/components/auth/register.vue +0 -4
- package/components/card/collections-more.vue +1 -1
- package/components/card/dropdown/index.vue +2 -2
- package/components/card/playlist.vue +7 -8
- package/components/card/video/actions.vue +2 -2
- package/components/card/video/index.vue +9 -9
- package/components/filter/popup.vue +1 -0
- package/components/library/feed.vue +1 -14
- package/components/playlist/add.vue +123 -113
- package/components/playlist/edit.vue +133 -199
- package/components/playlist/input.vue +23 -133
- package/components/playlist/main.vue +21 -40
- package/components/playlist/visibility.vue +149 -0
- package/components/section/collections.vue +2 -2
- package/components/section/swiper-cards.vue +3 -3
- package/components/ui/chips.vue +7 -2
- package/components/ui/confirm.vue +17 -5
- package/components/ui/dropdown.vue +17 -8
- package/components/ui/expand-row.vue +0 -4
- package/components/ui/popup.vue +33 -15
- package/components/ui/section-title.vue +6 -6
- package/components/ui/tooltip.vue +35 -14
- package/composables/__tests__/use-overlay-stack.test.ts +110 -0
- package/composables/__tests__/use-video-collections.test.ts +75 -0
- package/composables/use-overlay-stack.ts +58 -0
- package/composables/use-playlist-edit.ts +13 -3
- package/composables/use-snackbar.ts +15 -1
- package/composables/use-user.ts +0 -4
- package/composables/use-video-collections.ts +27 -5
- package/package.json +1 -1
- package/pages/following.vue +4 -4
- package/pages/history.vue +1 -1
- package/pages/library/index.vue +4 -4
- package/pages/playlists/index.vue +7 -76
- package/runtime/constants/playlist-type.ts +5 -0
- package/components/playlist/private-toggle.vue +0 -20
- package/composables/use-favorites.ts +0 -20
- package/pages/favorites.vue +0 -31
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<!-- Затемнение общее на весь флоу: у диалогов бэкдроп прозрачный, иначе при смене
|
|
3
|
+
попапа их подложки складываются и фон моргает. -->
|
|
4
|
+
<div
|
|
5
|
+
v-if="isPlaylistAdd || isPlaylistEditOpen"
|
|
6
|
+
class="playlist-main__decorate"
|
|
7
|
+
/>
|
|
8
|
+
|
|
2
9
|
<PlaylistAdd
|
|
3
10
|
:loading-post-playlist="loadingPostPlaylist"
|
|
4
11
|
:loading-user-playlists="loadingUserPlaylists"
|
|
@@ -6,18 +13,14 @@
|
|
|
6
13
|
:user-playlists="data"
|
|
7
14
|
@execute-user-playlists="getUserPlaylists"
|
|
8
15
|
@save-video-to-playlist="saveVideoToPlaylist"
|
|
9
|
-
@edit-playlist="saveEditPlaylist"
|
|
10
|
-
@delete-playlist="deletePlaylist"
|
|
11
16
|
/>
|
|
12
17
|
|
|
13
18
|
<PlaylistEdit
|
|
14
|
-
:loading-edit-videos="loadingPlaylistsVideos"
|
|
15
19
|
:loading-playlist-delete="loadingPlaylistDelete"
|
|
16
20
|
:loading-playlists="loadingUserPlaylists"
|
|
17
21
|
:loading-refresh="loadingRefresh"
|
|
18
22
|
@delete-playlist="deletePlaylist"
|
|
19
23
|
@save-edit="saveEditPlaylist"
|
|
20
|
-
@open-edit="openEdit"
|
|
21
24
|
/>
|
|
22
25
|
|
|
23
26
|
<PlaylistDeleteVideo
|
|
@@ -29,16 +32,17 @@
|
|
|
29
32
|
|
|
30
33
|
<script setup lang="ts">
|
|
31
34
|
import { PlaylistsApiService } from '../../services/api/playlists.service';
|
|
32
|
-
import type { Language } from '../../runtime';
|
|
33
|
-
import { AsyncData
|
|
35
|
+
import type { Language, PlaylistType } from '../../runtime';
|
|
36
|
+
import { AsyncData } from '../../runtime';
|
|
34
37
|
import { useI18n } from 'vue-i18n';
|
|
35
38
|
import type { IPlaylistVideoFormType, IPlaylistShort } from '../../types';
|
|
36
39
|
|
|
37
40
|
const { locale } = useI18n();
|
|
38
41
|
|
|
39
|
-
const
|
|
42
|
+
const { isPlaylistAdd } = usePlaylistAdd();
|
|
43
|
+
const { isPlaylistEditOpen } = usePlaylistEdit();
|
|
40
44
|
|
|
41
|
-
const
|
|
45
|
+
const route = useRoute();
|
|
42
46
|
|
|
43
47
|
const {
|
|
44
48
|
data,
|
|
@@ -68,22 +72,6 @@ async function getUserPlaylists() {
|
|
|
68
72
|
await executeUserPlaylists([locale.value as Language], AsyncData.GetPlaylists);
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
watch(data, (val) => {
|
|
72
|
-
if (!val) return;
|
|
73
|
-
const fav = val.items?.find(item => item.name === FAVORITES_KEY);
|
|
74
|
-
favoritesId.value = fav?.id ?? null;
|
|
75
|
-
favoritesFirstVideoId.value = fav?.firstVideoId ?? null;
|
|
76
|
-
}, { immediate: true });
|
|
77
|
-
|
|
78
|
-
async function ensureFavoritesPlaylist(): Promise<string | null> {
|
|
79
|
-
const existing = data.value?.items?.find(item => item.name === FAVORITES_KEY);
|
|
80
|
-
if (existing) return existing.id;
|
|
81
|
-
|
|
82
|
-
await executePostPlaylist([FAVORITES_KEY, PlaylistType.Private]);
|
|
83
|
-
await executeUserPlaylists([locale.value as Language], AsyncData.GetPlaylists);
|
|
84
|
-
return data.value?.items?.find(item => item.name === FAVORITES_KEY)?.id ?? null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
75
|
async function saveVideoToPlaylist(event: {
|
|
88
76
|
form: IPlaylistVideoFormType,
|
|
89
77
|
id: string,
|
|
@@ -98,16 +86,6 @@ async function saveVideoToPlaylist(event: {
|
|
|
98
86
|
if (newId) playlistIds.push(newId);
|
|
99
87
|
}
|
|
100
88
|
|
|
101
|
-
const favIdx = playlistIds.indexOf(FAVORITES_KEY);
|
|
102
|
-
if (favIdx !== -1) {
|
|
103
|
-
const favId = await ensureFavoritesPlaylist();
|
|
104
|
-
if (favId) {
|
|
105
|
-
playlistIds[favIdx] = favId;
|
|
106
|
-
} else {
|
|
107
|
-
playlistIds.splice(favIdx, 1);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
89
|
const form = { ...event.form, playlistId: playlistIds };
|
|
112
90
|
await executePostPlaylistVideo([form]);
|
|
113
91
|
|
|
@@ -145,11 +123,14 @@ async function saveEditPlaylist(event: {
|
|
|
145
123
|
page: 1,
|
|
146
124
|
}, locale.value as Language], `${AsyncData.GetPlaylistsVideo}-${event.id}`);
|
|
147
125
|
}
|
|
126
|
+
</script>
|
|
148
127
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
128
|
+
<style lang="scss">
|
|
129
|
+
.playlist-main__decorate {
|
|
130
|
+
position: fixed;
|
|
131
|
+
inset: 0;
|
|
132
|
+
z-index: $z-index-popup;
|
|
133
|
+
backdrop-filter: blur($blur-sm);
|
|
134
|
+
background-color: $popup-decorate-bg;
|
|
154
135
|
}
|
|
155
|
-
</
|
|
136
|
+
</style>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="playlist-visibility">
|
|
3
|
+
<UiToggle
|
|
4
|
+
:model-value="modelValue === PlaylistType.Public"
|
|
5
|
+
@update:model-value="emit('update:modelValue', $event ? PlaylistType.Public : PlaylistType.Private)"
|
|
6
|
+
>
|
|
7
|
+
<span class="playlist-visibility__label">
|
|
8
|
+
<UiIcon
|
|
9
|
+
:name="PLAYLIST_TYPE_ICON[PlaylistType.Public]"
|
|
10
|
+
class="playlist-visibility__icon"
|
|
11
|
+
size="16"
|
|
12
|
+
/>
|
|
13
|
+
{{ $t('public') }}
|
|
14
|
+
</span>
|
|
15
|
+
</UiToggle>
|
|
16
|
+
<UiTooltip
|
|
17
|
+
class="playlist-visibility__tooltip"
|
|
18
|
+
right
|
|
19
|
+
>
|
|
20
|
+
<template #trigger>
|
|
21
|
+
<span class="playlist-visibility__hint">?</span>
|
|
22
|
+
</template>
|
|
23
|
+
<p class="playlist-visibility__text">
|
|
24
|
+
<span
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
class="playlist-visibility__toggle --on"
|
|
27
|
+
/>
|
|
28
|
+
<span><strong>{{ $t('playlist.public_on') }}</strong> — {{ $t('playlist.public_on_hint') }}</span>
|
|
29
|
+
</p>
|
|
30
|
+
<p class="playlist-visibility__text">
|
|
31
|
+
<span
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
class="playlist-visibility__toggle"
|
|
34
|
+
/>
|
|
35
|
+
<span><strong>{{ $t('playlist.public_off') }}</strong> — {{ $t('playlist.public_off_hint') }}</span>
|
|
36
|
+
</p>
|
|
37
|
+
</UiTooltip>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { PLAYLIST_TYPE_ICON, PlaylistType } from '../../runtime';
|
|
43
|
+
|
|
44
|
+
defineProps<{
|
|
45
|
+
modelValue: PlaylistType
|
|
46
|
+
}>();
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits<{
|
|
49
|
+
(eventName: 'update:modelValue', value: PlaylistType): void
|
|
50
|
+
}>();
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<style lang="scss">
|
|
54
|
+
.playlist-visibility {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
flex-wrap: wrap;
|
|
58
|
+
gap: 10px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.playlist-visibility__label {
|
|
62
|
+
@include font-sm;
|
|
63
|
+
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
column-gap: 8px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.playlist-visibility__icon {
|
|
70
|
+
color: $playlist-visibility-icon;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.playlist-visibility__tooltip {
|
|
74
|
+
margin-left: 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.playlist-visibility__tooltip .ui-tooltip__content {
|
|
78
|
+
width: 288px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.playlist-visibility__hint {
|
|
82
|
+
@include font-xs;
|
|
83
|
+
|
|
84
|
+
display: inline-grid;
|
|
85
|
+
place-content: center;
|
|
86
|
+
width: 20px;
|
|
87
|
+
height: 20px;
|
|
88
|
+
border-radius: $radius-full;
|
|
89
|
+
color: $playlist-visibility-hint-text;
|
|
90
|
+
background-color: $playlist-visibility-hint-bg;
|
|
91
|
+
font-weight: 700;
|
|
92
|
+
line-height: 1;
|
|
93
|
+
cursor: help;
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
|
|
96
|
+
@include hover(background-color) {
|
|
97
|
+
background-color: $playlist-visibility-hint-bg--hover;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.playlist-visibility__text {
|
|
102
|
+
@include font-xs;
|
|
103
|
+
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: start;
|
|
106
|
+
column-gap: 10px;
|
|
107
|
+
line-height: 1.5;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.playlist-visibility__text:not(:last-child) {
|
|
111
|
+
margin-bottom: 10px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.playlist-visibility__text strong {
|
|
115
|
+
color: $playlist-visibility-tooltip-highlight;
|
|
116
|
+
font-weight: 700;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.playlist-visibility__toggle {
|
|
120
|
+
position: relative;
|
|
121
|
+
flex-shrink: 0;
|
|
122
|
+
width: 28px;
|
|
123
|
+
height: 16px;
|
|
124
|
+
margin-top: 2px;
|
|
125
|
+
border-radius: $radius-full;
|
|
126
|
+
background-color: $toggle-bg;
|
|
127
|
+
|
|
128
|
+
&::after {
|
|
129
|
+
content: '';
|
|
130
|
+
position: absolute;
|
|
131
|
+
top: 2px;
|
|
132
|
+
left: 2px;
|
|
133
|
+
width: 12px;
|
|
134
|
+
height: 12px;
|
|
135
|
+
border-radius: 50%;
|
|
136
|
+
background-color: $toggle-handle;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.playlist-visibility__toggle.--on {
|
|
141
|
+
background-color: $toggle-bg--active;
|
|
142
|
+
|
|
143
|
+
&::after {
|
|
144
|
+
left: auto;
|
|
145
|
+
right: 2px;
|
|
146
|
+
background-color: $toggle-handle--active;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<UiSectionTitle
|
|
9
9
|
:title="t('collections_by')"
|
|
10
10
|
title-tag="h2"
|
|
11
|
-
|
|
11
|
+
variant="section"
|
|
12
12
|
>
|
|
13
13
|
<span class="section-collections__badge">{{ t('editorial') }}</span>
|
|
14
14
|
</UiSectionTitle>
|
|
@@ -86,7 +86,7 @@ const restThumbs = computed(() => {
|
|
|
86
86
|
align-items: center;
|
|
87
87
|
justify-content: space-between;
|
|
88
88
|
column-gap: 16px;
|
|
89
|
-
margin-bottom:
|
|
89
|
+
margin-bottom: 16px;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
.section-collections__badge {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
:title="title || t('top_models')"
|
|
7
7
|
:count="count ?? items.length"
|
|
8
8
|
:icon="icon"
|
|
9
|
-
:
|
|
9
|
+
:variant="variant"
|
|
10
10
|
class="section-swiper-cards__title"
|
|
11
11
|
title-tag="h2"
|
|
12
12
|
/>
|
|
@@ -70,8 +70,8 @@ withDefaults(defineProps<{
|
|
|
70
70
|
/** Счётчик в шапке; по умолчанию — число карточек */
|
|
71
71
|
count?: string | number
|
|
72
72
|
icon?: string
|
|
73
|
-
/**
|
|
74
|
-
|
|
73
|
+
/** Вариант заголовка — см. `UiSectionTitle` */
|
|
74
|
+
variant?: 'page' | 'section'
|
|
75
75
|
placeholderAvatar?: string
|
|
76
76
|
/** Карточки только с аватаром и названием, без счётчика роликов */
|
|
77
77
|
withoutCount?: boolean
|
package/components/ui/chips.vue
CHANGED
|
@@ -184,11 +184,16 @@ const name = computed(() => {
|
|
|
184
184
|
@include font-xs;
|
|
185
185
|
|
|
186
186
|
padding-block: 4px;
|
|
187
|
-
padding-inline:
|
|
187
|
+
padding-inline: 10px;
|
|
188
188
|
height: var(--chips-height-micro);
|
|
189
|
-
border-radius:
|
|
189
|
+
border-radius: $radius-6;
|
|
190
190
|
background-color: $chips-micro-bg;
|
|
191
191
|
color: $chips-micro-text;
|
|
192
|
+
|
|
193
|
+
@include hover(background-color, color) {
|
|
194
|
+
background-color: $chips-micro-bg--hover;
|
|
195
|
+
color: $chips-micro-text--hover;
|
|
196
|
+
}
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
.ui-chips.--not-capitalize {
|
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
<LazyUiPopup
|
|
5
5
|
v-if="modelValue"
|
|
6
6
|
:model-value="modelValue"
|
|
7
|
+
:transparent-backdrop="transparentBackdrop"
|
|
8
|
+
class="ui-confirm"
|
|
7
9
|
sheet
|
|
8
10
|
@update:model-value="emit('update:modelValue', $event)"
|
|
9
11
|
>
|
|
10
12
|
<template #title>{{ title }}</template>
|
|
11
13
|
|
|
12
|
-
<
|
|
14
|
+
<div class="ui-confirm__body">
|
|
15
|
+
<slot/>
|
|
16
|
+
</div>
|
|
13
17
|
|
|
14
18
|
<template #footer>
|
|
15
19
|
<div class="ui-confirm__buttons">
|
|
@@ -41,6 +45,7 @@ defineProps<{
|
|
|
41
45
|
title: string
|
|
42
46
|
confirmText: string
|
|
43
47
|
loading?: boolean
|
|
48
|
+
transparentBackdrop?: boolean
|
|
44
49
|
}>();
|
|
45
50
|
|
|
46
51
|
const emit = defineEmits<{
|
|
@@ -50,12 +55,19 @@ const emit = defineEmits<{
|
|
|
50
55
|
</script>
|
|
51
56
|
|
|
52
57
|
<style lang="scss">
|
|
58
|
+
/* Паддинг на своей обёртке, а не на .ui-popup__content: у sheet-варианта он обнулён
|
|
59
|
+
селектором из четырёх классов, перебивать который дороже, чем задать отступ самому. */
|
|
60
|
+
.ui-confirm .ui-popup__content {
|
|
61
|
+
padding: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.ui-confirm__body {
|
|
65
|
+
padding: 24px;
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
.ui-confirm__buttons {
|
|
54
69
|
display: grid;
|
|
70
|
+
grid-template-columns: 1fr 1fr;
|
|
55
71
|
gap: 12px;
|
|
56
|
-
|
|
57
|
-
@include from-br(sm) {
|
|
58
|
-
grid-template-columns: 1fr 1fr;
|
|
59
|
-
}
|
|
60
72
|
}
|
|
61
73
|
</style>
|
|
@@ -89,6 +89,8 @@ onClickOutside(dropdownRef, () => {
|
|
|
89
89
|
|
|
90
90
|
const { scrollLock, scrollUnlock } = useGlobalScrollLock();
|
|
91
91
|
|
|
92
|
+
const { registerOverlay, releaseOverlay } = useOverlayStack();
|
|
93
|
+
|
|
92
94
|
const breakpoints = CSS_BREAKPOINTS;
|
|
93
95
|
const { isTablet } = checkDeviceWidth(breakpoints);
|
|
94
96
|
|
|
@@ -126,17 +128,22 @@ function close() {
|
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
watch(open, (val) => {
|
|
129
|
-
if (!isTablet.value) return;
|
|
130
131
|
if (val) {
|
|
132
|
+
if (!isTablet.value) return;
|
|
131
133
|
scrollLock();
|
|
134
|
+
// На мобиле меню — модальный шит с анимацией, поэтому он тоже участник стека
|
|
135
|
+
// оверлеев: попап, открытый из него, не должен появляться с анимацией.
|
|
136
|
+
nextTick(() => registerOverlay(menuRef.value));
|
|
132
137
|
} else {
|
|
133
|
-
scrollUnlock();
|
|
138
|
+
if (isTablet.value) scrollUnlock();
|
|
139
|
+
releaseOverlay(menuRef.value);
|
|
134
140
|
}
|
|
135
141
|
});
|
|
136
142
|
|
|
137
143
|
onBeforeUnmount(() => {
|
|
138
144
|
if (open.value && isTablet.value) {
|
|
139
145
|
scrollUnlock();
|
|
146
|
+
releaseOverlay(menuRef.value);
|
|
140
147
|
}
|
|
141
148
|
});
|
|
142
149
|
|
|
@@ -373,25 +380,27 @@ defineExpose({ openDropdown, open });
|
|
|
373
380
|
}
|
|
374
381
|
}
|
|
375
382
|
|
|
383
|
+
/* Гейты `--no-*-anim` вешает useOverlayStack: шит не анимируется, если он открывается
|
|
384
|
+
поверх попапа или закрывается, отдавая место другому оверлею. */
|
|
376
385
|
@include to-br(md) {
|
|
377
|
-
.ui-dropdown-enter-active,
|
|
378
|
-
.ui-dropdown-leave-active {
|
|
386
|
+
.ui-dropdown__menu:where(:not(.--no-enter-anim)).ui-dropdown-enter-active,
|
|
387
|
+
.ui-dropdown__menu:where(:not(.--no-leave-anim)).ui-dropdown-leave-active {
|
|
379
388
|
transition: transform 300ms ease;
|
|
380
389
|
}
|
|
381
390
|
|
|
382
|
-
.ui-dropdown-enter-from {
|
|
391
|
+
.ui-dropdown__menu:where(:not(.--no-enter-anim)).ui-dropdown-enter-from {
|
|
383
392
|
transform: translateY(100%);
|
|
384
393
|
}
|
|
385
394
|
|
|
386
|
-
.ui-dropdown-enter-to {
|
|
395
|
+
.ui-dropdown__menu:where(:not(.--no-enter-anim)).ui-dropdown-enter-to {
|
|
387
396
|
transform: translateY(0);
|
|
388
397
|
}
|
|
389
398
|
|
|
390
|
-
.ui-dropdown-leave-from {
|
|
399
|
+
.ui-dropdown__menu:where(:not(.--no-leave-anim)).ui-dropdown-leave-from {
|
|
391
400
|
transform: translateY(0);
|
|
392
401
|
}
|
|
393
402
|
|
|
394
|
-
.ui-dropdown-leave-to {
|
|
403
|
+
.ui-dropdown__menu:where(:not(.--no-leave-anim)).ui-dropdown-leave-to {
|
|
395
404
|
transform: translateY(100%);
|
|
396
405
|
}
|
|
397
406
|
}
|
package/components/ui/popup.vue
CHANGED
|
@@ -73,7 +73,9 @@ import { onBackdropClick } from '../../runtime';
|
|
|
73
73
|
|
|
74
74
|
const { scrollLock, scrollUnlock } = useGlobalScrollLock();
|
|
75
75
|
|
|
76
|
-
const {
|
|
76
|
+
const { snackbarText, isSnackBarInPopup, enterPopup, leavePopup } = useSnackbar();
|
|
77
|
+
|
|
78
|
+
const { registerOverlay, releaseOverlay } = useOverlayStack();
|
|
77
79
|
|
|
78
80
|
const popupRef = ref();
|
|
79
81
|
const emit = defineEmits<{
|
|
@@ -93,9 +95,9 @@ const props = defineProps<{
|
|
|
93
95
|
}>();
|
|
94
96
|
|
|
95
97
|
onMounted(() => {
|
|
96
|
-
|
|
97
|
-
resetSnackbar();
|
|
98
|
+
enterPopup();
|
|
98
99
|
scrollLock();
|
|
100
|
+
registerOverlay(popupRef.value);
|
|
99
101
|
if (props.notModal) {
|
|
100
102
|
popupRef.value.show();
|
|
101
103
|
} else {
|
|
@@ -105,8 +107,9 @@ onMounted(() => {
|
|
|
105
107
|
});
|
|
106
108
|
|
|
107
109
|
onBeforeUnmount(() => {
|
|
108
|
-
|
|
110
|
+
leavePopup();
|
|
109
111
|
scrollUnlock();
|
|
112
|
+
releaseOverlay(popupRef.value);
|
|
110
113
|
});
|
|
111
114
|
|
|
112
115
|
function close() {
|
|
@@ -118,8 +121,6 @@ function closeByBackdropClick(event: Event) {
|
|
|
118
121
|
const target = event.target as HTMLDialogElement;
|
|
119
122
|
onBackdropClick(target, close);
|
|
120
123
|
}
|
|
121
|
-
|
|
122
|
-
const { snackbarText, isSnackBarInPopup } = useSnackbar();
|
|
123
124
|
</script>
|
|
124
125
|
|
|
125
126
|
<style lang="scss">
|
|
@@ -167,7 +168,7 @@ const { snackbarText, isSnackBarInPopup } = useSnackbar();
|
|
|
167
168
|
|
|
168
169
|
margin: $margin-block auto;
|
|
169
170
|
max-height: calc(100vh - $margin-block * 2);
|
|
170
|
-
min-width: $popup-min-width;
|
|
171
|
+
min-width: var(--popup-min-width, #{$popup-min-width});
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
@include to-br(sm) {
|
|
@@ -354,14 +355,14 @@ const { snackbarText, isSnackBarInPopup } = useSnackbar();
|
|
|
354
355
|
padding: 20px;
|
|
355
356
|
}
|
|
356
357
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
358
|
+
/* Появление анимируется только у первого оверлея в стеке, закрытие — только у последнего
|
|
359
|
+
(см. useOverlayStack): иначе на переходе попап→попап диалоги едут навстречу друг другу. */
|
|
360
|
+
.ui-popup.--drawer:where(:not(.--no-enter-anim)) {
|
|
361
|
+
&.v-enter-active {
|
|
360
362
|
transition: transform 300ms ease;
|
|
361
363
|
}
|
|
362
364
|
|
|
363
|
-
&.v-enter-active::backdrop
|
|
364
|
-
&.v-leave-active::backdrop {
|
|
365
|
+
&.v-enter-active::backdrop {
|
|
365
366
|
transition: opacity 300ms ease;
|
|
366
367
|
}
|
|
367
368
|
|
|
@@ -376,6 +377,16 @@ const { snackbarText, isSnackBarInPopup } = useSnackbar();
|
|
|
376
377
|
&.v-enter-to {
|
|
377
378
|
transform: translateX(0);
|
|
378
379
|
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.ui-popup.--drawer:where(:not(.--no-leave-anim)) {
|
|
383
|
+
&.v-leave-active {
|
|
384
|
+
transition: transform 300ms ease;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
&.v-leave-active::backdrop {
|
|
388
|
+
transition: opacity 300ms ease;
|
|
389
|
+
}
|
|
379
390
|
|
|
380
391
|
&.v-leave-from {
|
|
381
392
|
transform: translateX(0);
|
|
@@ -390,10 +401,9 @@ const { snackbarText, isSnackBarInPopup } = useSnackbar();
|
|
|
390
401
|
}
|
|
391
402
|
}
|
|
392
403
|
|
|
393
|
-
.ui-popup.--sheet {
|
|
404
|
+
.ui-popup.--sheet:where(:not(.--no-enter-anim)) {
|
|
394
405
|
@include to-br(sm) {
|
|
395
|
-
&.v-enter-active
|
|
396
|
-
&.v-leave-active {
|
|
406
|
+
&.v-enter-active {
|
|
397
407
|
transition: transform 300ms ease;
|
|
398
408
|
}
|
|
399
409
|
|
|
@@ -404,6 +414,14 @@ const { snackbarText, isSnackBarInPopup } = useSnackbar();
|
|
|
404
414
|
&.v-enter-to {
|
|
405
415
|
transform: translateY(0);
|
|
406
416
|
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.ui-popup.--sheet:where(:not(.--no-leave-anim)) {
|
|
421
|
+
@include to-br(sm) {
|
|
422
|
+
&.v-leave-active {
|
|
423
|
+
transition: transform 300ms ease;
|
|
424
|
+
}
|
|
407
425
|
|
|
408
426
|
&.v-leave-from {
|
|
409
427
|
transform: translateY(0);
|
|
@@ -38,16 +38,16 @@ const props = withDefaults(defineProps<{
|
|
|
38
38
|
title: string
|
|
39
39
|
icon?: string
|
|
40
40
|
img?: string
|
|
41
|
-
/** `
|
|
42
|
-
|
|
41
|
+
/** `page` — страничный заголовок (18px, капс, 700); `section` — заголовок секции (18px, без капса, 600) */
|
|
42
|
+
variant?: 'page' | 'section'
|
|
43
43
|
}>(), {
|
|
44
44
|
titleTag: 'h1',
|
|
45
|
-
|
|
45
|
+
variant: 'page'
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
const titleClass = computed(() => [
|
|
49
49
|
'ui-section-title__title',
|
|
50
|
-
props.
|
|
50
|
+
props.variant === 'section' ? '--section' : '_title'
|
|
51
51
|
]);
|
|
52
52
|
</script>
|
|
53
53
|
|
|
@@ -59,8 +59,8 @@ const titleClass = computed(() => [
|
|
|
59
59
|
vertical-align: middle;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
.ui-section-title__title.--
|
|
63
|
-
@include font-
|
|
62
|
+
.ui-section-title__title.--section {
|
|
63
|
+
@include font-lg;
|
|
64
64
|
|
|
65
65
|
font-weight: 600;
|
|
66
66
|
}
|