feat: Update articles page UI (#13049)
This commit is contained in:
@@ -22,10 +22,10 @@ const handleClick = () => {
|
||||
class="flex flex-col w-full shadow outline-1 outline outline-n-container group/cardLayout rounded-2xl bg-n-solid-2"
|
||||
>
|
||||
<div
|
||||
class="flex w-full gap-3 py-5"
|
||||
class="flex w-full gap-2 py-4"
|
||||
:class="[
|
||||
layout === 'col' ? 'flex-col' : 'flex-row justify-between items-center',
|
||||
selectable ? 'px-10 py-6' : 'px-6',
|
||||
selectable ? 'px-10 py-6' : 'px-4',
|
||||
]"
|
||||
@click="handleClick"
|
||||
>
|
||||
|
||||
@@ -102,6 +102,7 @@ const openFilter = () => {
|
||||
:current-page="currentPage"
|
||||
:total-items="totalItems"
|
||||
:items-per-page="itemsPerPage"
|
||||
class="max-w-[105rem]"
|
||||
@update:current-page="updateCurrentPage"
|
||||
/>
|
||||
</footer>
|
||||
|
||||
@@ -93,13 +93,15 @@ const statusText = computed(() => {
|
||||
|
||||
const categoryName = computed(() => {
|
||||
if (props.category?.slug) {
|
||||
return `${props.category.icon} ${props.category.name}`;
|
||||
return props.category.name;
|
||||
}
|
||||
return t(
|
||||
'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.CATEGORY.UNCATEGORISED'
|
||||
);
|
||||
});
|
||||
|
||||
const categoryIcon = computed(() => props.category?.icon || '');
|
||||
|
||||
const authorName = computed(() => {
|
||||
return props.author?.name || props.author?.availableName || '';
|
||||
});
|
||||
@@ -123,17 +125,17 @@ const handleClick = id => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardLayout>
|
||||
<CardLayout class="[&>div]:!pt-3 group/articleCard hover:bg-n-alpha-1">
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<span
|
||||
class="text-base cursor-pointer hover:underline underline-offset-2 hover:text-n-blue-text text-n-slate-12 line-clamp-1"
|
||||
class="text-sm cursor-pointer py-1.5 font-medium underline-offset-2 group-hover/articleCard:text-n-blue-11 text-n-slate-12 line-clamp-1"
|
||||
@click="handleClick(id)"
|
||||
>
|
||||
{{ title }}
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="text-xs font-medium inline-flex items-center h-6 px-2 py-0.5 rounded-md bg-n-alpha-2"
|
||||
class="text-xs font-440 outline outline-1 outline-n-container inline-flex items-center px-2 h-6 rounded-md bg-n-button-color"
|
||||
:class="statusTextColor"
|
||||
>
|
||||
{{ statusText }}
|
||||
@@ -144,41 +146,52 @@ const handleClick = id => {
|
||||
>
|
||||
<Button
|
||||
icon="i-lucide-ellipsis-vertical"
|
||||
color="slate"
|
||||
slate
|
||||
:variant="!showActionsDropdown ? 'ghost' : 'faded'"
|
||||
size="xs"
|
||||
class="rounded-md group-hover:bg-n-alpha-2"
|
||||
class="rounded-md group-hover:bg-n-alpha-2 [&>span]:size-3.5"
|
||||
@click="toggleDropdown()"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showActionsDropdown"
|
||||
:menu-items="articleMenuItems"
|
||||
class="mt-1 ltr:right-0 rtl:left-0 xl:ltr:left-0 xl:rtl:right-0 top-full"
|
||||
class="mt-1 ltr:right-0 rtl:left-0 top-full"
|
||||
@action="handleArticleAction($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between w-full gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex items-center justify-between w-full gap-4 min-w-0">
|
||||
<div class="flex items-center gap-3 min-w-0 overflow-hidden">
|
||||
<div class="flex items-center gap-1.5 min-w-0 shrink">
|
||||
<Avatar
|
||||
:name="authorName"
|
||||
:src="authorThumbnailSrc"
|
||||
:size="16"
|
||||
rounded-full
|
||||
class="shrink-0"
|
||||
/>
|
||||
<span class="text-sm truncate text-n-slate-11">
|
||||
<span class="text-sm font-420 truncate text-n-slate-11">
|
||||
{{ authorName || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="block text-sm whitespace-nowrap text-n-slate-11">
|
||||
{{ categoryName }}
|
||||
</span>
|
||||
<div class="w-px h-3 bg-n-weak rounded-lg shrink-0" />
|
||||
<div
|
||||
class="inline-flex items-center gap-1 text-n-slate-11 whitespace-nowrap"
|
||||
v-if="category"
|
||||
class="flex items-center gap-1.5 text-n-slate-11 min-w-0 shrink"
|
||||
>
|
||||
<Icon icon="i-lucide-eye" class="size-4" />
|
||||
<span class="text-sm">
|
||||
<span v-if="categoryIcon" class="text-sm shrink-0">
|
||||
{{ categoryIcon }}
|
||||
</span>
|
||||
<Icon v-else icon="i-lucide-shapes" class="size-4 shrink-0" />
|
||||
<span class="text-sm font-420 truncate">
|
||||
{{ categoryName }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="category" class="w-px h-3 bg-n-weak rounded-lg shrink-0" />
|
||||
<div class="flex items-center gap-1.5 text-n-slate-11 shrink-0">
|
||||
<Icon icon="i-lucide-trending-up" class="size-4" />
|
||||
<span class="text-sm font-420 whitespace-nowrap">
|
||||
{{
|
||||
t('HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.VIEWS', {
|
||||
count: views,
|
||||
@@ -187,7 +200,7 @@ const handleClick = id => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-sm text-n-slate-11 line-clamp-1">
|
||||
<span class="text-sm text-n-slate-11 truncate">
|
||||
{{ lastUpdatedAt }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,14 @@ defineProps({
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
headerTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
createButtonLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
totalItems: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
@@ -32,7 +40,7 @@ defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:currentPage']);
|
||||
const emit = defineEmits(['update:currentPage', 'create']);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
@@ -55,48 +63,75 @@ const updateCurrentPage = page => {
|
||||
const togglePortalSwitcher = () => {
|
||||
showPortalSwitcher.value = !showPortalSwitcher.value;
|
||||
};
|
||||
|
||||
const onClickCreateButton = () => {
|
||||
emit('create');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-surface-1">
|
||||
<header class="sticky top-0 z-10 px-6 pb-3 lg:px-0">
|
||||
<div class="w-full max-w-[60rem] mx-auto lg:px-6">
|
||||
<div
|
||||
v-if="showHeaderTitle"
|
||||
class="flex items-center justify-start h-20 gap-2"
|
||||
>
|
||||
<span
|
||||
v-if="activePortalName"
|
||||
class="text-xl font-medium text-n-slate-12"
|
||||
<header
|
||||
class="sticky top-0 z-10 px-6 pb-3 lg:px-0 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none"
|
||||
>
|
||||
<div class="w-full max-w-5xl mx-auto lg:px-6">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div
|
||||
v-if="showHeaderTitle"
|
||||
class="flex items-center justify-start h-20 gap-1"
|
||||
>
|
||||
{{ activePortalName }}
|
||||
</span>
|
||||
<div v-if="activePortalName" class="relative group">
|
||||
<OnClickOutside @trigger="showPortalSwitcher = false">
|
||||
<Button
|
||||
icon="i-lucide-chevron-down"
|
||||
variant="ghost"
|
||||
color="slate"
|
||||
size="xs"
|
||||
class="rounded-md group-hover:bg-n-slate-3 hover:bg-n-slate-3"
|
||||
@click="togglePortalSwitcher"
|
||||
/>
|
||||
<span
|
||||
v-if="activePortalName"
|
||||
class="text-lg font-520 text-n-slate-12 ltr:mr-1 rtl:ml-1"
|
||||
>
|
||||
{{ activePortalName }}
|
||||
</span>
|
||||
<div v-if="activePortalName" class="relative group">
|
||||
<OnClickOutside @trigger="showPortalSwitcher = false">
|
||||
<Button
|
||||
icon="i-lucide-chevron-down"
|
||||
variant="ghost"
|
||||
color="slate"
|
||||
size="xs"
|
||||
class="rounded-md group-hover:bg-n-slate-3 hover:bg-n-slate-3 [&>span]:size-4"
|
||||
@click="togglePortalSwitcher"
|
||||
/>
|
||||
|
||||
<PortalSwitcher
|
||||
v-if="showPortalSwitcher"
|
||||
class="absolute ltr:left-0 rtl:right-0 top-9"
|
||||
@close="showPortalSwitcher = false"
|
||||
@create-portal="createPortalDialogRef.dialogRef.open()"
|
||||
<PortalSwitcher
|
||||
v-if="showPortalSwitcher"
|
||||
class="absolute ltr:left-0 rtl:right-0 top-9"
|
||||
@close="showPortalSwitcher = false"
|
||||
@create-portal="createPortalDialogRef.dialogRef.open()"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
<CreatePortalDialog ref="createPortalDialogRef" />
|
||||
</div>
|
||||
<div
|
||||
v-if="activePortalName && headerTitle"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<div
|
||||
v-if="activePortalName"
|
||||
class="w-px h-3 rounded-2xl bg-n-strong ltr:ml-2 ltr:mr-3 rtl:mr-2 rtl:ml-3"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
<CreatePortalDialog ref="createPortalDialogRef" />
|
||||
<span v-if="headerTitle" class="text-sm font-420 text-n-slate-12">
|
||||
{{ headerTitle }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
v-if="createButtonLabel"
|
||||
:label="createButtonLabel"
|
||||
icon="i-lucide-plus"
|
||||
size="sm"
|
||||
@click="onClickCreateButton"
|
||||
/>
|
||||
</div>
|
||||
<slot name="header-actions" />
|
||||
</div>
|
||||
</header>
|
||||
<main class="flex-1 px-6 overflow-y-auto lg:px-0">
|
||||
<div class="w-full max-w-[60rem] mx-auto py-3 lg:px-6">
|
||||
<div class="w-full max-w-5xl mx-auto py-3 lg:px-6">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</main>
|
||||
@@ -105,6 +140,7 @@ const togglePortalSwitcher = () => {
|
||||
:current-page="currentPage"
|
||||
:total-items="totalItems"
|
||||
:items-per-page="itemsPerPage"
|
||||
class="max-w-5xl"
|
||||
@update:current-page="updateCurrentPage"
|
||||
/>
|
||||
</footer>
|
||||
|
||||
+7
-20
@@ -29,12 +29,7 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'tabChange',
|
||||
'localeChange',
|
||||
'categoryChange',
|
||||
'newArticle',
|
||||
]);
|
||||
const emit = defineEmits(['tabChange', 'localeChange', 'categoryChange']);
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
@@ -126,23 +121,21 @@ const handleCategoryAction = ({ value }) => {
|
||||
isCategoryMenuOpen.value = false;
|
||||
};
|
||||
|
||||
const handleNewArticle = () => {
|
||||
emit('newArticle');
|
||||
};
|
||||
|
||||
const handleTabChange = value => {
|
||||
emit('tabChange', value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-start w-full gap-2 lg:flex-row">
|
||||
<div
|
||||
class="flex flex-col items-start justify-between w-full gap-2 lg:flex-row"
|
||||
>
|
||||
<TabBar
|
||||
:tabs="tabs"
|
||||
:initial-active-tab="activeTabIndex"
|
||||
@tab-changed="handleTabChange"
|
||||
/>
|
||||
<div class="flex items-start justify-between w-full gap-2">
|
||||
<div class="flex items-start justify-between lg:justify-end w-full gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="relative group">
|
||||
<OnClickOutside @trigger="isLocaleMenuOpen = false">
|
||||
@@ -159,7 +152,7 @@ const handleTabChange = value => {
|
||||
v-if="isLocaleMenuOpen"
|
||||
:menu-items="localeMenuItems"
|
||||
show-search
|
||||
class="left-0 w-40 max-w-[300px] mt-2 overflow-y-auto xl:right-0 top-full max-h-60"
|
||||
class="ltr:left-0 rtl:right-0 w-40 max-w-[300px] mt-2 overflow-y-auto top-full max-h-60"
|
||||
@action="handleLocaleAction"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
@@ -180,18 +173,12 @@ const handleTabChange = value => {
|
||||
v-if="isCategoryMenuOpen"
|
||||
:menu-items="categoryMenuItems"
|
||||
show-search
|
||||
class="left-0 w-48 mt-2 overflow-y-auto xl:right-0 top-full max-h-60"
|
||||
class="ltr:right-0 rtl:left-0 w-48 mt-2 overflow-y-auto top-full max-h-60"
|
||||
@action="handleCategoryAction"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
:label="t('HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.NEW_ARTICLE')"
|
||||
icon="i-lucide-plus"
|
||||
size="sm"
|
||||
@click="handleNewArticle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+5
-1
@@ -136,7 +136,12 @@ const navigateToNewArticlePage = () => {
|
||||
:total-items="articlesCount"
|
||||
:items-per-page="25"
|
||||
:header="portalName"
|
||||
:header-title="t('HELP_CENTER.ARTICLES_PAGE.HEADER')"
|
||||
:create-button-label="
|
||||
t('HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.NEW_ARTICLE')
|
||||
"
|
||||
:show-pagination-footer="shouldShowPaginationFooter"
|
||||
@create="navigateToNewArticlePage"
|
||||
@update:current-page="handlePageChange"
|
||||
>
|
||||
<template #header-actions>
|
||||
@@ -149,7 +154,6 @@ const navigateToNewArticlePage = () => {
|
||||
@tab-change="handleTabChange"
|
||||
@locale-change="handleLocaleAction"
|
||||
@category-change="handleCategoryAction"
|
||||
@new-article="navigateToNewArticlePage"
|
||||
/>
|
||||
<CategoryHeaderControls
|
||||
v-else-if="showCategoryHeaderControls"
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ const handleDeletePortal = () => {
|
||||
</div>
|
||||
<div
|
||||
v-else-if="activePortal"
|
||||
class="flex flex-col w-full gap-4 max-w-[40rem] pb-8"
|
||||
class="flex flex-col w-full gap-4 max-w-2xl pb-8"
|
||||
>
|
||||
<PortalBaseSettings
|
||||
:active-portal="activePortal"
|
||||
|
||||
@@ -71,7 +71,7 @@ const pageInfo = computed(() => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex justify-between h-[3.375rem] w-full max-w-[105rem] border-t border-n-weak mx-auto bg-n-surface-1 py-3 px-6 items-center before:absolute before:inset-x-0 before:-top-4 before:bg-gradient-to-t before:from-n-surface-1 before:from-10% before:dark:from-0% before:to-transparent before:h-4 before:pointer-events-none"
|
||||
class="flex justify-between h-[3.375rem] w-full border-t border-n-weak mx-auto bg-n-surface-1 py-3 px-6 items-center before:absolute before:inset-x-0 before:-top-4 before:bg-gradient-to-t before:from-n-surface-1 before:from-0% before:to-transparent before:h-4 before:pointer-events-none"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="min-w-0 text-sm font-420 line-clamp-1 text-n-slate-11">
|
||||
|
||||
@@ -77,7 +77,7 @@ const showDivider = index => {
|
||||
<template v-for="(tab, index) in tabs" :key="index">
|
||||
<button
|
||||
:ref="el => (tabRefs[index] = el)"
|
||||
class="relative z-10 px-4 truncate py-1.5 text-sm border-0 outline-1 outline-transparent rounded-lg transition-all duration-200 ease-out hover:text-n-brand active:scale-[1.02]"
|
||||
class="relative z-10 px-4 truncate py-1.5 font-460 text-sm border-0 outline-1 outline-transparent rounded-lg transition-all duration-200 ease-out hover:text-n-brand active:scale-[1.02]"
|
||||
:class="[
|
||||
activeTab === index
|
||||
? 'text-n-blue-11 scale-100'
|
||||
|
||||
@@ -496,6 +496,7 @@
|
||||
},
|
||||
"LOADING": "Loading...",
|
||||
"ARTICLES_PAGE": {
|
||||
"HEADER": "Articles",
|
||||
"ARTICLE_CARD": {
|
||||
"CARD": {
|
||||
"VIEWS": "{count} view | {count} views",
|
||||
@@ -839,7 +840,7 @@
|
||||
"STATUS": {
|
||||
"UPLOADED": "Ready",
|
||||
"PROCESSING": "Processing",
|
||||
"PROCESSED": "Completed",
|
||||
"PROCESSED": "Completed",
|
||||
"FAILED": "Failed"
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user