itube-specs 0.0.893 → 0.0.895

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.
@@ -964,18 +964,22 @@ $library-item-badge-text: $color-white;
964
964
  $library-item-badge-bordered-border: $color-zinc-600;
965
965
  $library-item-badge-bordered-text: $color-zinc-400;
966
966
 
967
- // ─── Library model (блок подписки на модель) ───
968
- $library-model-bg: $color-zinc-900-40;
969
- $library-model-border: $color-zinc-800;
970
- $library-model-avatar-border: $color-zinc-700;
971
- $library-model-avatar-border--hover: $color-zinc-500;
972
- $library-model-name: $color-white;
973
- $library-model-name--hover: $color-indigo-400;
974
- $library-model-badge-bg: $color-indigo-600;
975
- $library-model-badge-text: $color-white;
976
- $library-model-tags: $color-zinc-400;
977
- $library-model-meta: $color-zinc-500;
978
- $library-model-meta-dot: $color-zinc-600;
967
+ // ─── Library feed (блок подписки: модель / канал / категория) ───
968
+ $library-feed-bg: $color-zinc-900-40;
969
+ $library-feed-border: $color-zinc-800;
970
+ $library-feed-fade: $color-zinc-950;
971
+ $library-feed-avatar-border: $color-zinc-700;
972
+ $library-feed-avatar-border--hover: $color-zinc-500;
973
+ $library-feed-initials: $color-zinc-400;
974
+ $library-feed-initials-gradient-from: $color-zinc-700;
975
+ $library-feed-initials-gradient-to: $color-zinc-800;
976
+ $library-feed-name: $color-white;
977
+ $library-feed-name--hover: $color-indigo-400;
978
+ $library-feed-badge-bg: $color-indigo-600;
979
+ $library-feed-badge-text: $color-white;
980
+ $library-feed-meta: $color-zinc-500;
981
+ $library-feed-meta-dot: $color-zinc-600;
982
+ $library-feed-title: $color-zinc-500;
979
983
 
980
984
  // ─── Page: library ───
981
985
  $page-library-heading: $color-white;
@@ -994,18 +994,22 @@ $library-item-badge-text: $color-white;
994
994
  $library-item-badge-bordered-border: $color-gray-300;
995
995
  $library-item-badge-bordered-text: $color-gray-600;
996
996
 
997
- // ─── Library model (блок подписки на модель) ───
998
- $library-model-bg: $color-gray-100;
999
- $library-model-border: transparent;
1000
- $library-model-avatar-border: $color-gray-300;
1001
- $library-model-avatar-border--hover: $color-indigo-500-50;
1002
- $library-model-name: $color-gray-900;
1003
- $library-model-name--hover: $color-indigo-600;
1004
- $library-model-badge-bg: $color-indigo-600;
1005
- $library-model-badge-text: $color-white;
1006
- $library-model-tags: $color-gray-600;
1007
- $library-model-meta: $color-gray-500;
1008
- $library-model-meta-dot: $color-gray-400;
997
+ // ─── Library feed (блок подписки: модель / канал / категория) ───
998
+ $library-feed-bg: $color-gray-100;
999
+ $library-feed-border: transparent;
1000
+ $library-feed-fade: $color-gray-100;
1001
+ $library-feed-avatar-border: $color-gray-300;
1002
+ $library-feed-avatar-border--hover: $color-indigo-500-50;
1003
+ $library-feed-initials: $color-gray-600;
1004
+ $library-feed-initials-gradient-from: $color-gray-200;
1005
+ $library-feed-initials-gradient-to: $color-gray-300;
1006
+ $library-feed-name: $color-gray-900;
1007
+ $library-feed-name--hover: $color-indigo-600;
1008
+ $library-feed-badge-bg: $color-indigo-600;
1009
+ $library-feed-badge-text: $color-white;
1010
+ $library-feed-meta: $color-gray-500;
1011
+ $library-feed-meta-dot: $color-gray-400;
1012
+ $library-feed-title: $color-gray-500;
1009
1013
 
1010
1014
  // ─── Page: library ───
1011
1015
  $page-library-heading: $color-gray-900;
@@ -0,0 +1,340 @@
1
+ <!-- Блок подписки (модель / канал / категория): шапка с аватаром, тегами и счётчиками + лента роликов -->
2
+ <template>
3
+ <article class="library-feed">
4
+ <header class="library-feed__header">
5
+ <NuxtLink
6
+ :to="item.to"
7
+ :title="item.title"
8
+ class="library-feed__avatar-link"
9
+ >
10
+ <UiImg
11
+ v-if="currentAvatar"
12
+ :src="currentAvatar"
13
+ :alt="item.title"
14
+ :loading="priority ? 'eager' : 'lazy'"
15
+ :priority="priority"
16
+ class="library-feed__avatar"
17
+ width="96"
18
+ height="96"
19
+ sizes="96px"
20
+ @error="avatarError = true"
21
+ />
22
+ <span
23
+ v-else
24
+ class="library-feed__avatar library-feed__initials"
25
+ >{{ initials }}</span>
26
+ </NuxtLink>
27
+
28
+ <div class="library-feed__info">
29
+ <div class="library-feed__heading">
30
+ <NuxtLink
31
+ :to="item.to"
32
+ class="library-feed__name"
33
+ >
34
+ {{ item.title }}
35
+ <img
36
+ v-if="item.country"
37
+ :alt="item.country.title"
38
+ :title="`${t('birthplace_country')}: ${item.country.title}`"
39
+ :src="`/icons/flags/${item.country.name}.svg`"
40
+ class="library-feed__flag"
41
+ width="20"
42
+ height="14"
43
+ >
44
+ </NuxtLink>
45
+
46
+ <span
47
+ v-if="item.newCount"
48
+ class="library-feed__badge"
49
+ >{{ t('library.new_count', { count: item.newCount }) }}</span>
50
+ </div>
51
+
52
+ <LazyUiExpandRow
53
+ v-if="item.tags && item.tags.length"
54
+ :items="item.tags"
55
+ class="library-feed__tags"
56
+ hydrate-on-visible
57
+ micro
58
+ />
59
+
60
+ <p class="library-feed__meta">
61
+ <span
62
+ v-for="(entry, index) in item.meta"
63
+ :key="`meta-${index}`"
64
+ class="library-feed__meta-item"
65
+ >
66
+ <UiIcon
67
+ v-if="entry.icon"
68
+ :name="entry.icon"
69
+ size="14"
70
+ />
71
+ {{ entry.text }}
72
+ </span>
73
+ </p>
74
+ </div>
75
+
76
+ <div class="library-feed__actions _from-sm">
77
+ <UiBtn
78
+ :theme="following ? 'secondary' : 'primary'"
79
+ class="library-feed__follow"
80
+ @click="emit('toggle-follow', item)"
81
+ >
82
+ <UiIcon
83
+ :name="following ? 'check' : 'plus'"
84
+ size="16"
85
+ />
86
+ {{ following ? t('library.following_state') : t('follow') }}
87
+ </UiBtn>
88
+
89
+ <UiLink
90
+ :to="item.to"
91
+ theme="secondary"
92
+ >
93
+ {{ videosText }}
94
+ <UiIcon
95
+ name="arrow-right"
96
+ size="16"
97
+ />
98
+ </UiLink>
99
+ </div>
100
+ </header>
101
+
102
+ <h3
103
+ v-if="item.feedTitle"
104
+ class="library-feed__feed-title"
105
+ >{{ item.feedTitle }}</h3>
106
+
107
+ <div class="library-feed__grid">
108
+ <CardVideo
109
+ v-for="(video, index) in item.videos"
110
+ :key="video.guid"
111
+ :card="video"
112
+ :loading="priority && index < 3 ? 'eager' : 'lazy'"
113
+ :priority="priority && index < 3"
114
+ compact
115
+ />
116
+ </div>
117
+
118
+ <UiLink
119
+ :to="item.to"
120
+ theme="secondary"
121
+ class="library-feed__all _to-sm"
122
+ wide
123
+ >
124
+ {{ videosText }}
125
+ <UiIcon
126
+ name="arrow-right"
127
+ size="16"
128
+ />
129
+ </UiLink>
130
+ </article>
131
+ </template>
132
+
133
+ <script setup lang="ts">
134
+ import type { ILibraryFeedItem } from '../../types';
135
+
136
+ const props = defineProps<{
137
+ item: ILibraryFeedItem
138
+ following?: boolean
139
+ priority?: boolean
140
+ }>();
141
+
142
+ const emit = defineEmits<{
143
+ (eventName: 'toggle-follow', item: ILibraryFeedItem): void,
144
+ }>();
145
+
146
+ const { t } = useI18n();
147
+
148
+ const avatarError = ref(false);
149
+
150
+ const currentAvatar = computed(() => avatarError.value ? undefined : props.item.avatarUrl);
151
+
152
+ /** Заглушка вместо аватара: «Top To Bottom» → TTB, «Bareback» → B. */
153
+ const initials = computed(() => props.item.title
154
+ .split(/\s+/)
155
+ .slice(0, 3)
156
+ .map(word => word.charAt(0))
157
+ .join('')
158
+ .toUpperCase());
159
+
160
+ const videosText = computed(() => t('plural.videos', props.item.videosCount));
161
+ </script>
162
+
163
+ <style lang="scss">
164
+ .library-feed {
165
+ /* Растушёвка «ещё» у тегов уходит в фон карточки, а не страницы */
166
+ --expand-row-fade-end: #{$library-feed-fade};
167
+
168
+ padding: 16px;
169
+ background-color: $library-feed-bg;
170
+ border: 1px solid $library-feed-border;
171
+ border-radius: $radius-xl;
172
+
173
+ @include from-br(sm) {
174
+ padding: 20px;
175
+ }
176
+ }
177
+
178
+ .library-feed__header {
179
+ display: grid;
180
+ grid-template-columns: auto minmax(0, 1fr);
181
+ column-gap: 16px;
182
+ row-gap: 12px;
183
+ align-items: start;
184
+ margin-bottom: 20px;
185
+
186
+ @include from-br(sm) {
187
+ grid-template-columns: auto minmax(0, 1fr) auto;
188
+ }
189
+ }
190
+
191
+ .library-feed__avatar {
192
+ width: 80px;
193
+ height: 80px;
194
+ object-fit: cover;
195
+ object-position: center 30%;
196
+ border-radius: $radius-full;
197
+ border: 2px solid $library-feed-avatar-border;
198
+
199
+ @include from-br(sm) {
200
+ width: 96px;
201
+ height: 96px;
202
+ transition: var(--transition) border-color;
203
+ }
204
+ }
205
+
206
+ @include from-br(sm) {
207
+ .library-feed__avatar-link:is(:hover, :focus-visible) .library-feed__avatar {
208
+ border-color: $library-feed-avatar-border--hover;
209
+ }
210
+ }
211
+
212
+ .library-feed__initials {
213
+ @include font-lg;
214
+
215
+ display: grid;
216
+ place-content: center;
217
+ font-weight: 700;
218
+ text-transform: uppercase;
219
+ color: $library-feed-initials;
220
+ background: linear-gradient(to bottom right, $library-feed-initials-gradient-from, $library-feed-initials-gradient-to);
221
+ }
222
+
223
+ .library-feed__info {
224
+ min-width: 0;
225
+ }
226
+
227
+ .library-feed__heading {
228
+ display: flex;
229
+ flex-wrap: wrap;
230
+ align-items: center;
231
+ column-gap: 10px;
232
+ row-gap: 6px;
233
+ }
234
+
235
+ .library-feed__name {
236
+ @include font-lg;
237
+
238
+ display: flex;
239
+ align-items: center;
240
+ column-gap: 8px;
241
+ font-weight: 700;
242
+ text-transform: uppercase;
243
+ color: $library-feed-name;
244
+
245
+ @include from-br(sm) {
246
+ transition: var(--transition) color;
247
+ }
248
+
249
+ @include hover(color) {
250
+ color: $library-feed-name--hover;
251
+ }
252
+ }
253
+
254
+ .library-feed__flag {
255
+ flex-shrink: 0;
256
+ border-radius: 2px;
257
+ }
258
+
259
+ .library-feed__badge {
260
+ @include font-xs;
261
+
262
+ flex-shrink: 0;
263
+ font-weight: 600;
264
+ white-space: nowrap;
265
+ padding-block: 4px;
266
+ padding-inline: 10px;
267
+ color: $library-feed-badge-text;
268
+ background-color: $library-feed-badge-bg;
269
+ border-radius: $radius-full;
270
+ }
271
+
272
+ .library-feed__tags {
273
+ margin-top: 8px;
274
+ }
275
+
276
+ .library-feed__meta {
277
+ @include font-sm;
278
+
279
+ display: flex;
280
+ flex-wrap: wrap;
281
+ align-items: center;
282
+ column-gap: 8px;
283
+ row-gap: 4px;
284
+ margin-top: 8px;
285
+ color: $library-feed-meta;
286
+ }
287
+
288
+ .library-feed__meta-item {
289
+ display: flex;
290
+ align-items: center;
291
+ column-gap: 6px;
292
+ }
293
+
294
+ .library-feed__meta-item:not(:last-child)::after {
295
+ content: '·';
296
+ margin-inline-start: 2px;
297
+ color: $library-feed-meta-dot;
298
+ }
299
+
300
+ .library-feed__actions {
301
+ display: flex;
302
+ align-items: center;
303
+ column-gap: 8px;
304
+ }
305
+
306
+ .library-feed__follow {
307
+ min-width: 124px;
308
+ }
309
+
310
+ .library-feed__feed-title {
311
+ @include font-sm;
312
+
313
+ margin: 0 0 12px;
314
+ font-weight: 400;
315
+ color: $library-feed-title;
316
+ }
317
+
318
+ .library-feed__grid {
319
+ display: grid;
320
+ gap: 16px;
321
+ grid-template-columns: repeat(2, 1fr);
322
+
323
+ @include from-br(xm) {
324
+ grid-template-columns: repeat(3, 1fr);
325
+ }
326
+
327
+ @include from-br(sm) {
328
+ gap: 24px 16px;
329
+ grid-template-columns: repeat(4, 1fr);
330
+ }
331
+
332
+ @include from-br(md) {
333
+ grid-template-columns: repeat(5, 1fr);
334
+ }
335
+ }
336
+
337
+ .library-feed__all {
338
+ margin-top: 16px;
339
+ }
340
+ </style>
@@ -0,0 +1,147 @@
1
+ <!-- Список подписок «Библиотеки»: сортировка, блоки лент, подтверждение отписки -->
2
+ <template>
3
+ <div class="library-follows">
4
+ <UiSectionTitle
5
+ :title="title"
6
+ :count="items.length"
7
+ :icon="icon"
8
+ class="library-follows__title"
9
+ />
10
+
11
+ <div class="library-follows__sort">
12
+ <UiBtn
13
+ v-for="option in sortItems"
14
+ :key="option.name"
15
+ :theme="sort === option.name ? 'primary' : 'secondary'"
16
+ @click="sort = option.name"
17
+ >{{ option.title }}</UiBtn>
18
+ </div>
19
+
20
+ <div
21
+ v-if="sortedItems.length > 0"
22
+ class="library-follows__list"
23
+ >
24
+ <LibraryFeed
25
+ v-for="(item, index) in sortedItems"
26
+ :key="item.guid"
27
+ :item="item"
28
+ :following="isFollowing(item)"
29
+ :priority="index === 0"
30
+ @toggle-follow="onToggleFollow"
31
+ />
32
+ </div>
33
+
34
+ <UiEmptyState
35
+ v-else
36
+ without-reset
37
+ />
38
+
39
+ <UiConfirm
40
+ v-model="isUnfollowOpen"
41
+ :title="t('library.unfollow_title', { name: pendingUnfollow?.title })"
42
+ :confirm-text="t('library.unfollow_confirm')"
43
+ @confirm="onUnfollowConfirm"
44
+ >
45
+ <p class="library-follows__confirm">{{ t('library.unfollow_text') }}</p>
46
+ </UiConfirm>
47
+ </div>
48
+ </template>
49
+
50
+ <script setup lang="ts">
51
+ import type { ILibraryFeedItem } from '../../types';
52
+
53
+ const LibrarySort = {
54
+ New: 'new',
55
+ Alphabet: 'alphabet',
56
+ } as const;
57
+
58
+ type LibrarySortValue = typeof LibrarySort[keyof typeof LibrarySort];
59
+
60
+ const props = defineProps<{
61
+ items: ILibraryFeedItem[]
62
+ title: string
63
+ icon: string
64
+ }>();
65
+
66
+ const { t } = useI18n();
67
+
68
+ const sort = ref<LibrarySortValue>(LibrarySort.New);
69
+
70
+ const sortItems = computed(() => [
71
+ { name: LibrarySort.New, title: t('library.sort_new_first') },
72
+ { name: LibrarySort.Alphabet, title: t('library.sort_alphabet') },
73
+ ]);
74
+
75
+ /**
76
+ * «Сначала новые» — по числу непросмотренных новинок, а не по дате обновления: подписка
77
+ * с тремя новыми роликами важнее свежей, но с одним. Порядок равных сохраняет сортировка.
78
+ */
79
+ const sortedItems = computed(() => {
80
+ const items = [...props.items];
81
+
82
+ return sort.value === LibrarySort.Alphabet
83
+ ? items.sort((a, b) => a.title.localeCompare(b.title))
84
+ : items.sort((a, b) => b.newCount - a.newCount);
85
+ });
86
+
87
+ const unfollowed = ref<string[]>([]);
88
+
89
+ function isFollowing(item: ILibraryFeedItem) {
90
+ return !unfollowed.value.includes(item.guid);
91
+ }
92
+
93
+ /** Сущность, для которой открыт попап отписки. */
94
+ const pendingUnfollow = ref<ILibraryFeedItem | null>(null);
95
+
96
+ const isUnfollowOpen = computed({
97
+ get: () => pendingUnfollow.value !== null,
98
+ set: value => {
99
+ if (!value) pendingUnfollow.value = null;
100
+ },
101
+ });
102
+
103
+ /** Подписка — сразу, отписка — только через подтверждение: она сносит ленту обновлений. */
104
+ function onToggleFollow(item: ILibraryFeedItem) {
105
+ if (isFollowing(item)) {
106
+ pendingUnfollow.value = item;
107
+ return;
108
+ }
109
+
110
+ unfollowed.value = unfollowed.value.filter(guid => guid !== item.guid);
111
+ }
112
+
113
+ function onUnfollowConfirm() {
114
+ if (!pendingUnfollow.value) return;
115
+
116
+ unfollowed.value = [...unfollowed.value, pendingUnfollow.value.guid];
117
+ pendingUnfollow.value = null;
118
+ }
119
+ </script>
120
+
121
+ <style lang="scss">
122
+ .library-follows__title {
123
+ margin-bottom: 16px;
124
+ }
125
+
126
+ .library-follows__sort {
127
+ display: flex;
128
+ flex-wrap: wrap;
129
+ align-items: center;
130
+ gap: 8px;
131
+ margin-bottom: 24px;
132
+ }
133
+
134
+ .library-follows__list {
135
+ display: grid;
136
+ row-gap: 24px;
137
+ }
138
+
139
+ .library-follows__confirm {
140
+ @include font-sm;
141
+
142
+ max-width: 400px;
143
+ margin: 0 auto;
144
+ text-align: center;
145
+ color: $page-library-confirm-text;
146
+ }
147
+ </style>
@@ -5,6 +5,7 @@
5
5
  { '--expand-show': isExpandOpened },
6
6
  { '--alphabet': alphabet },
7
7
  { '--mobile-expand': mobileExpand },
8
+ { '--micro': micro },
8
9
  ]"
9
10
  class="ui-expand-row"
10
11
  >
@@ -27,12 +28,13 @@
27
28
  :alphabet="alphabet"
28
29
  is-link
29
30
  :mini="mini"
31
+ :micro="micro"
30
32
  />
31
33
  </slot>
32
34
  <div
33
35
  v-if="!hideMore"
34
36
  v-show="isElementsOverflow"
35
- :class="{'--small': mini}"
37
+ :class="{'--small': mini, '--micro': micro}"
36
38
  class="ui-expand-row__more-wrapper"
37
39
  >
38
40
  <UiChips
@@ -41,6 +43,7 @@
41
43
  icon: isExpandOpened ? 'minus' : 'plus',
42
44
  }"
43
45
  :mini="mini"
46
+ :micro="micro"
44
47
  class="ui-expand-row__button --more"
45
48
  @click="onMoreClick"
46
49
  />
@@ -57,6 +60,7 @@ defineProps<{
57
60
  alphabet?: boolean;
58
61
  hideMore?: boolean;
59
62
  mini?: boolean;
63
+ micro?: boolean;
60
64
  mobileExpand?: boolean;
61
65
  preText?: string;
62
66
  }>();
@@ -167,6 +171,12 @@ onBeforeUnmount(() => {
167
171
  }
168
172
  }
169
173
 
174
+ /* Обрезка идёт по высоте одной строки, а она зависит от размера чипса — иначе из-под края
175
+ выглядывает следующий ряд. Правило стоит до `--expand-show`: у них равная специфичность. */
176
+ .ui-expand-row.--micro {
177
+ max-height: var(--chips-height-micro);
178
+ }
179
+
170
180
  .ui-expand-row__wrapper {
171
181
  display: inline-flex;
172
182
  flex-wrap: wrap;
@@ -193,7 +203,7 @@ onBeforeUnmount(() => {
193
203
  height: var(--chips-height);
194
204
  z-index: $z-index-plus;
195
205
  padding-left: 40px;
196
- background: linear-gradient(90deg, $expand-row-fade-start 0%, $expand-row-fade-end 22.64%);
206
+ background: linear-gradient(90deg, $expand-row-fade-start 0%, var(--expand-row-fade-end, #{$expand-row-fade-end}) 22.64%);
197
207
  }
198
208
 
199
209
  .ui-expand-row__more-wrapper.--small {
@@ -204,6 +214,15 @@ onBeforeUnmount(() => {
204
214
  }
205
215
  }
206
216
 
217
+ .ui-expand-row__more-wrapper.--micro {
218
+ top: 0;
219
+ height: var(--chips-height-micro);
220
+ }
221
+
222
+ .ui-expand-row.--micro .ui-expand-row__wrapper {
223
+ gap: 4px;
224
+ }
225
+
207
226
  .ui-expand-row__button.--more {
208
227
  text-transform: capitalize;
209
228
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.893",
4
+ "version": "0.0.895",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
@@ -0,0 +1,61 @@
1
+ <!-- Подписки на категории: широкие показывают лучшее за неделю, узкие — новинки. Данные пока заглушка -->
2
+ <template>
3
+ <div class="_page _container">
4
+ <UiBreadcrumbs
5
+ :items="breadcrumbs"
6
+ class="_breadcrumbs"
7
+ />
8
+
9
+ <LibraryFollows
10
+ :items="items"
11
+ :title="t('library.my_categories')"
12
+ icon="grid"
13
+ />
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { getLibraryCategoriesStub } from '../../stubs/library-follows';
19
+ import { convertString, formatDate } from '../../runtime';
20
+ import type { IBreadcrumbItem, ILibraryFeedItem } from '../../types';
21
+
22
+ const { t } = useI18n();
23
+ const { generateLink } = useGenerateLink();
24
+
25
+ const items = computed<ILibraryFeedItem[]>(() => getLibraryCategoriesStub().map(category => ({
26
+ guid: category.guid,
27
+ title: category.title,
28
+ to: generateLink(`/categories/${convertString().toSlug(category.title)}`),
29
+ newCount: category.newCount,
30
+ meta: [
31
+ { icon: 'video-camera', text: String(category.videosCount) },
32
+ { icon: 'date', text: t('library.updated_at', { date: formatDate(category.lastVideoAddedAt) }) },
33
+ ],
34
+ videosCount: category.videosCount,
35
+ // Нечего показать в новинках — лента откатывается на лучшее за неделю
36
+ feedTitle: category.newCount ? t('library.latest') : t('library.top_this_week'),
37
+ videos: category.videos,
38
+ })));
39
+
40
+ useNoindex();
41
+
42
+ const { brandName } = useAppConfig();
43
+
44
+ useHead(() => ({
45
+ title: `${t('library.my_categories')} — ${brandName}`,
46
+ }));
47
+
48
+ const breadcrumbs = [
49
+ {
50
+ name: `${t('navigation.main')}`,
51
+ to: '/',
52
+ },
53
+ {
54
+ name: `${t('navigation.library')}`,
55
+ to: '/library',
56
+ },
57
+ {
58
+ name: `${t('library.my_categories')}`,
59
+ },
60
+ ] as IBreadcrumbItem[];
61
+ </script>