feat: Update categories page UI (#13052)
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
|
||||
import CardLayout from 'dashboard/components-next/CardLayout.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -38,27 +37,6 @@ const emit = defineEmits(['click', 'action']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const [showActionsDropdown, toggleDropdown] = useToggle();
|
||||
|
||||
const categoryMenuItems = [
|
||||
{
|
||||
label: 'Edit',
|
||||
action: 'edit',
|
||||
value: 'edit',
|
||||
icon: 'i-lucide-pencil',
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
action: 'delete',
|
||||
value: 'delete',
|
||||
icon: 'i-lucide-trash',
|
||||
},
|
||||
];
|
||||
|
||||
const categoryTitleWithIcon = computed(() => {
|
||||
return `${props.icon} ${props.title}`;
|
||||
});
|
||||
|
||||
const description = computed(() => {
|
||||
return props.description ? props.description : 'No description added';
|
||||
});
|
||||
@@ -71,59 +49,68 @@ const handleClick = slug => {
|
||||
emit('click', slug);
|
||||
};
|
||||
|
||||
const handleAction = ({ action, value }) => {
|
||||
emit('action', { action, value, id: props.id });
|
||||
toggleDropdown(false);
|
||||
const handleAction = ({ action }) => {
|
||||
emit('action', { action, id: props.id });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardLayout>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="flex justify-between w-full gap-2">
|
||||
<div class="flex items-center justify-start w-full min-w-0 gap-2">
|
||||
<span
|
||||
class="text-base truncate cursor-pointer hover:underline underline-offset-2 hover:text-n-blue-text text-n-slate-12"
|
||||
@click="handleClick(slug)"
|
||||
>
|
||||
{{ categoryTitleWithIcon }}
|
||||
</span>
|
||||
<span
|
||||
class="inline-flex items-center justify-center h-6 px-2 py-1 text-xs text-center border rounded-lg bg-n-slate-1 whitespace-nowrap shrink-0 text-n-slate-11 border-n-slate-4"
|
||||
>
|
||||
{{
|
||||
t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_CARD.ARTICLES_COUNT', {
|
||||
count: articlesCount,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<div class="flex items-start justify-between gap-3 group/categoryCard">
|
||||
<div
|
||||
class="size-10 rounded-[0.625rem] mt-1 outline outline-1 outline-n-weak flex items-center justify-center flex-shrink-0"
|
||||
>
|
||||
<span v-if="icon" class="text-sm font-420">
|
||||
{{ icon }}
|
||||
</span>
|
||||
<Icon v-else icon="i-lucide-shapes" class="size-4 shrink-0" />
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<div class="flex justify-between w-full gap-2 h-6">
|
||||
<div class="flex items-center justify-start min-w-0 gap-2">
|
||||
<span
|
||||
class="text-sm font-medium truncate group-hover/categoryCard:text-n-blue-11 cursor-pointer text-n-slate-12"
|
||||
@click="handleClick(slug)"
|
||||
>
|
||||
{{ title }}
|
||||
</span>
|
||||
<div class="w-px h-3 bg-n-weak rounded-lg shrink-0" />
|
||||
<span class="text-sm text-n-slate-11 font-420 shrink-0">
|
||||
{{
|
||||
t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_CARD.ARTICLES_COUNT', {
|
||||
count: articlesCount,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 shrink-0">
|
||||
<Button
|
||||
icon="i-lucide-pen-line"
|
||||
slate
|
||||
xs
|
||||
ghost
|
||||
class="[&>span]:size-3.5"
|
||||
@click="handleAction({ action: 'edit' })"
|
||||
/>
|
||||
<div class="w-px h-3 bg-n-weak rounded-lg" />
|
||||
<Button
|
||||
icon="i-lucide-trash"
|
||||
slate
|
||||
xs
|
||||
ghost
|
||||
class="[&>span]:size-3.5"
|
||||
@click="handleAction({ action: 'delete' })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-on-clickaway="() => toggleDropdown(false)"
|
||||
class="relative group"
|
||||
|
||||
<span
|
||||
class="text-sm line-clamp-3"
|
||||
:class="hasDescription ? 'text-n-slate-11' : 'text-n-slate-9'"
|
||||
>
|
||||
<Button
|
||||
icon="i-lucide-ellipsis-vertical"
|
||||
color="slate"
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
class="rounded-md group-hover:bg-n-alpha-2"
|
||||
@click="toggleDropdown()"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showActionsDropdown"
|
||||
:menu-items="categoryMenuItems"
|
||||
class="mt-1 ltr:right-0 rtl:left-0 xl:ltr:left-0 xl:rtl:right-0 top-full z-60"
|
||||
@action="handleAction"
|
||||
/>
|
||||
</div>
|
||||
{{ description }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="text-sm line-clamp-3"
|
||||
:class="hasDescription ? 'text-n-slate-11' : 'text-n-slate-9'"
|
||||
>
|
||||
{{ description }}
|
||||
</span>
|
||||
</CardLayout>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, useTemplateRef } from 'vue';
|
||||
import { OnClickOutside } from '@vueuse/components';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||
@@ -45,6 +45,7 @@ const emit = defineEmits(['update:currentPage', 'create']);
|
||||
const route = useRoute();
|
||||
|
||||
const createPortalDialogRef = ref(null);
|
||||
const createButtonRef = useTemplateRef('createButtonRef');
|
||||
|
||||
const showPortalSwitcher = ref(false);
|
||||
|
||||
@@ -119,6 +120,16 @@ const onClickCreateButton = () => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="createButtonRef" class="relative z-20">
|
||||
<Button
|
||||
v-if="createButtonLabel"
|
||||
:label="createButtonLabel"
|
||||
icon="i-lucide-plus"
|
||||
size="sm"
|
||||
@click="onClickCreateButton"
|
||||
/>
|
||||
<slot name="modal" :button-ref="createButtonRef" />
|
||||
</div>
|
||||
<Button
|
||||
v-if="createButtonLabel"
|
||||
:label="createButtonLabel"
|
||||
|
||||
+55
-1
@@ -1,5 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { useStoreGetters } from 'dashboard/composables/store.js';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
@@ -12,6 +14,7 @@ import CategoryHeaderControls from 'dashboard/components-next/HelpCenter/Pages/C
|
||||
import CategoryEmptyState from 'dashboard/components-next/HelpCenter/EmptyState/Category/CategoryEmptyState.vue';
|
||||
import EditCategoryDialog from 'dashboard/components-next/HelpCenter/Pages/CategoryPage/EditCategoryDialog.vue';
|
||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||
import CategoryDialog from 'dashboard/components-next/HelpCenter/Pages/CategoryPage/CategoryDialog.vue';
|
||||
|
||||
const props = defineProps({
|
||||
categories: {
|
||||
@@ -37,6 +40,36 @@ const { t } = useI18n();
|
||||
|
||||
const editCategoryDialog = ref(null);
|
||||
const selectedCategory = ref(null);
|
||||
const isCreateCategoryDialogOpen = ref(false);
|
||||
|
||||
const getters = useStoreGetters();
|
||||
|
||||
const currentPortalSlug = computed(() => route.params.portalSlug);
|
||||
|
||||
const currentPortal = computed(() => {
|
||||
const slug = currentPortalSlug.value;
|
||||
if (slug) return getters['portals/portalBySlug'].value(slug);
|
||||
return getters['portals/allPortals'].value[0];
|
||||
});
|
||||
|
||||
const currentPortalName = computed(() => currentPortal.value?.name);
|
||||
|
||||
const activeLocale = computed(() => {
|
||||
return props.allowedLocales.find(
|
||||
locale => locale.code === route.params.locale
|
||||
);
|
||||
});
|
||||
|
||||
const activeLocaleName = computed(() => activeLocale.value?.name ?? '');
|
||||
const activeLocaleCode = computed(() => activeLocale.value?.code ?? '');
|
||||
|
||||
const toggleCreateCategoryDialog = () => {
|
||||
isCreateCategoryDialogOpen.value = !isCreateCategoryDialogOpen.value;
|
||||
};
|
||||
|
||||
const closeCreateCategoryDialog = () => {
|
||||
isCreateCategoryDialogOpen.value = false;
|
||||
};
|
||||
|
||||
const isSwitchingPortal = useMapGetter('portals/isSwitchingPortal');
|
||||
const isLoading = computed(() => props.isFetching || isSwitchingPortal.value);
|
||||
@@ -101,7 +134,28 @@ const handleAction = ({ action, id, category: categoryData }) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HelpCenterLayout :show-pagination-footer="false">
|
||||
<HelpCenterLayout
|
||||
:header-title="t('HELP_CENTER.CATEGORY_PAGE.HEADER')"
|
||||
:create-button-label="
|
||||
t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.NEW_CATEGORY')
|
||||
"
|
||||
:show-pagination-footer="false"
|
||||
@create="toggleCreateCategoryDialog"
|
||||
>
|
||||
<template #modal="{ buttonRef }">
|
||||
<CategoryDialog
|
||||
v-if="isCreateCategoryDialogOpen"
|
||||
v-on-click-outside="[
|
||||
closeCreateCategoryDialog,
|
||||
{ ignore: [buttonRef] },
|
||||
]"
|
||||
mode="create"
|
||||
:portal-name="currentPortalName"
|
||||
:active-locale-name="activeLocaleName"
|
||||
:active-locale-code="activeLocaleCode"
|
||||
@close="closeCreateCategoryDialog"
|
||||
/>
|
||||
</template>
|
||||
<template #header-actions>
|
||||
<CategoryHeaderControls
|
||||
:categories="categories"
|
||||
|
||||
+15
-30
@@ -33,7 +33,6 @@ const getters = useStoreGetters();
|
||||
const { t } = useI18n();
|
||||
|
||||
const isLocaleMenuOpen = ref(false);
|
||||
const isCreateCategoryDialogOpen = ref(false);
|
||||
const isEditCategoryDialogOpen = ref(false);
|
||||
|
||||
const currentPortalSlug = computed(() => {
|
||||
@@ -127,7 +126,19 @@ const handleBreadcrumbClick = () => {
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div v-if="!hasSelectedCategory" class="flex items-center gap-4">
|
||||
<div
|
||||
v-if="!hasSelectedCategory"
|
||||
class="flex items-center gap-4 w-full justify-between"
|
||||
>
|
||||
<span
|
||||
class="min-w-0 text-sm font-medium truncate text-n-slate-11 ltr:ml-1 rtl:mr-1"
|
||||
>
|
||||
{{
|
||||
t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.CATEGORIES_COUNT', {
|
||||
n: categoriesCount,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<div class="relative group">
|
||||
<OnClickOutside @trigger="isLocaleMenuOpen = false">
|
||||
<Button
|
||||
@@ -142,44 +153,18 @@ const handleBreadcrumbClick = () => {
|
||||
v-if="isLocaleMenuOpen"
|
||||
:menu-items="localeMenuItems"
|
||||
show-search
|
||||
class="left-0 w-40 mt-2 overflow-y-auto xl:right-0 top-full max-h-60"
|
||||
class="ltr:right-0 rtl:left-0 w-40 mt-2 overflow-y-auto top-full max-h-60"
|
||||
@action="handleLocaleAction"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
</div>
|
||||
<div class="w-px h-3.5 rounded my-auto bg-n-weak" />
|
||||
<span class="min-w-0 text-sm font-medium truncate text-n-slate-12">
|
||||
{{
|
||||
t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.CATEGORIES_COUNT', {
|
||||
n: categoriesCount,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<Breadcrumb
|
||||
v-else
|
||||
:items="breadcrumbItems"
|
||||
@click="handleBreadcrumbClick"
|
||||
/>
|
||||
<div v-if="!hasSelectedCategory" class="relative">
|
||||
<OnClickOutside @trigger="isCreateCategoryDialogOpen = false">
|
||||
<Button
|
||||
:label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.NEW_CATEGORY')"
|
||||
icon="i-lucide-plus"
|
||||
size="sm"
|
||||
@click="isCreateCategoryDialogOpen = !isCreateCategoryDialogOpen"
|
||||
/>
|
||||
<CategoryDialog
|
||||
v-if="isCreateCategoryDialogOpen"
|
||||
mode="create"
|
||||
:portal-name="currentPortalName"
|
||||
:active-locale-name="activeLocaleName"
|
||||
:active-locale-code="activeLocaleCode"
|
||||
@close="isCreateCategoryDialogOpen = false"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
</div>
|
||||
<div v-else class="relative">
|
||||
<div v-if="hasSelectedCategory" class="relative">
|
||||
<OnClickOutside @trigger="isEditCategoryDialogOpen = false">
|
||||
<Button
|
||||
:label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.EDIT_CATEGORY')"
|
||||
|
||||
+3
-3
@@ -14,13 +14,13 @@ const handleClick = slug => {
|
||||
emit('click', slug);
|
||||
};
|
||||
|
||||
const handleAction = ({ action, value, id }, category) => {
|
||||
emit('action', { action, value, id, category });
|
||||
const handleAction = ({ action, id }, category) => {
|
||||
emit('action', { action, id, category });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul role="list" class="grid w-full h-full grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<ul role="list" class="flex w-full h-full flex-col gap-4">
|
||||
<CategoryCard
|
||||
v-for="category in categories"
|
||||
:id="category.id"
|
||||
|
||||
@@ -561,6 +561,7 @@
|
||||
}
|
||||
},
|
||||
"CATEGORY_PAGE": {
|
||||
"HEADER": "Categories",
|
||||
"CATEGORY_HEADER": {
|
||||
"NEW_CATEGORY": "New category",
|
||||
"EDIT_CATEGORY": "Edit category",
|
||||
|
||||
Reference in New Issue
Block a user