feat: Update button component (#10362)

This commit is contained in:
Sivin Varghese
2024-10-29 14:00:24 +05:30
committed by GitHub
parent f73798a1aa
commit 0689f59a05
34 changed files with 477 additions and 488 deletions

View File

@@ -1,6 +1,5 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { OnClickOutside } from '@vueuse/components';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { dynamicTime } from 'shared/helpers/timeHelper'; import { dynamicTime } from 'shared/helpers/timeHelper';
import { import {
@@ -9,11 +8,11 @@ import {
ARTICLE_STATUSES, ARTICLE_STATUSES,
} from 'dashboard/helper/portalHelper'; } from 'dashboard/helper/portalHelper';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import CardLayout from 'dashboard/components-next/CardLayout.vue'; import CardLayout from 'dashboard/components-next/CardLayout.vue';
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue'; import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
import Thumbnail from 'dashboard/components-next/thumbnail/Thumbnail.vue'; import Thumbnail from 'dashboard/components-next/thumbnail/Thumbnail.vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
const props = defineProps({ const props = defineProps({
id: { id: {
@@ -72,11 +71,11 @@ const articleMenuItems = computed(() => {
const statusTextColor = computed(() => { const statusTextColor = computed(() => {
switch (props.status) { switch (props.status) {
case 'archived': case 'archived':
return '!text-n-slate-12'; return 'text-n-slate-12';
case 'draft': case 'draft':
return '!text-n-amber-11'; return 'text-n-amber-11';
default: default:
return '!text-n-teal-11'; return 'text-n-teal-11';
} }
}); });
@@ -127,19 +126,28 @@ const handleClick = id => {
<template #header> <template #header>
<div class="flex justify-between gap-1"> <div class="flex justify-between gap-1">
<span <span
class="text-base cursor-pointer hover:underline text-n-slate-12 line-clamp-1" class="text-base cursor-pointer hover:underline underline-offset-2 hover:text-n-blue-text text-n-slate-12 line-clamp-1"
@click="handleClick(id)" @click="handleClick(id)"
> >
{{ title }} {{ title }}
</span> </span>
<div class="relative group" @click.stop> <div class="flex items-center gap-2">
<OnClickOutside @trigger="isOpen = false"> <span
class="text-xs font-medium inline-flex items-center h-6 px-2 py-0.5 rounded-md bg-n-alpha-2"
:class="statusTextColor"
>
{{ statusText }}
</span>
<div
v-on-clickaway="() => (isOpen = false)"
class="relative flex items-center group"
@click.stop
>
<Button <Button
variant="ghost" icon="i-lucide-ellipsis-vertical"
size="sm" color="slate"
class="text-xs font-medium bg-n-alpha-2 hover:bg-n-alpha-1 !h-6 rounded-md border-0 !px-2 !py-0.5" size="xs"
:label="statusText" class="group-hover:bg-n-solid-2 !p-0.5 rounded-md"
:class="statusTextColor"
@click="isOpen = !isOpen" @click="isOpen = !isOpen"
/> />
<DropdownMenu <DropdownMenu
@@ -148,7 +156,7 @@ const handleClick = id => {
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 xl:ltr:left-0 xl:rtl:right-0 top-full"
@action="handleArticleAction($event)" @action="handleArticleAction($event)"
/> />
</OnClickOutside> </div>
</div> </div>
</div> </div>
</template> </template>
@@ -171,7 +179,7 @@ const handleClick = id => {
<div <div
class="inline-flex items-center gap-1 text-n-slate-11 whitespace-nowrap" class="inline-flex items-center gap-1 text-n-slate-11 whitespace-nowrap"
> >
<FluentIcon icon="eye-show" size="18" /> <Icon icon="i-lucide-eye" class="size-4" />
<span class="text-sm"> <span class="text-sm">
{{ {{
t('HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.VIEWS', { t('HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.VIEWS', {

View File

@@ -84,7 +84,7 @@ const handleAction = ({ action, value }) => {
<div class="flex justify-between w-full gap-1"> <div class="flex justify-between w-full gap-1">
<div class="flex items-center justify-start gap-2"> <div class="flex items-center justify-start gap-2">
<span <span
class="text-base cursor-pointer hover:underline text-slate-900 dark:text-slate-50 line-clamp-1" class="text-base cursor-pointer hover:underline underline-offset-2 hover:text-n-blue-text text-n-slate-12 line-clamp-1"
@click="handleClick(slug)" @click="handleClick(slug)"
> >
{{ categoryTitleWithIcon }} {{ categoryTitleWithIcon }}
@@ -102,10 +102,10 @@ const handleAction = ({ action, value }) => {
<div class="relative group" @click.stop> <div class="relative group" @click.stop>
<OnClickOutside @trigger="isOpen = false"> <OnClickOutside @trigger="isOpen = false">
<Button <Button
variant="ghost" icon="i-lucide-ellipsis-vertical"
size="sm" color="slate"
icon="more-vertical" size="xs"
class="w-8 z-60 group-hover:bg-slate-100 dark:group-hover:bg-slate-800" class="rounded-md group-hover:bg-n-solid-2"
@click="isOpen = !isOpen" @click="isOpen = !isOpen"
/> />
<DropdownMenu <DropdownMenu

View File

@@ -49,12 +49,7 @@ const onClick = () => {
</template> </template>
<template #actions> <template #actions>
<div v-if="showButton"> <div v-if="showButton">
<Button <Button :label="buttonLabel" icon="i-lucide-plus" @click="onClick" />
variant="default"
:label="buttonLabel"
icon="add"
@click="onClick"
/>
</div> </div>
</template> </template>
</EmptyStateLayout> </EmptyStateLayout>

View File

@@ -59,9 +59,8 @@ const onPortalCreate = ({ slug: portalSlug, locale }) => {
</template> </template>
<template #actions> <template #actions>
<Button <Button
variant="default"
:label="$t('HELP_CENTER.NEW_PAGE.CREATE_PORTAL_BUTTON')" :label="$t('HELP_CENTER.NEW_PAGE.CREATE_PORTAL_BUTTON')"
icon="add" icon="i-lucide-plus"
@click="openDialog" @click="openDialog"
/> />
<CreatePortalDialog <CreatePortalDialog

View File

@@ -74,10 +74,10 @@ const togglePortalSwitcher = () => {
<div v-if="activePortalName" class="relative group"> <div v-if="activePortalName" class="relative group">
<OnClickOutside @trigger="showPortalSwitcher = false"> <OnClickOutside @trigger="showPortalSwitcher = false">
<Button <Button
icon="chevron-lucide-down" icon="i-lucide-chevron-down"
variant="ghost" variant="ghost"
icon-lib="lucide" size="xs"
class="!w-6 !h-6 group-hover:bg-n-solid-2 !p-0.5 rounded-md" class="rounded-md group-hover:bg-n-slate-3 hover:bg-n-slate-3"
@click="togglePortalSwitcher" @click="togglePortalSwitcher"
/> />

View File

@@ -52,7 +52,7 @@ const handleAction = ({ action, value }) => {
</script> </script>
<template> <template>
<CardLayout class="ltr:pr-2 rtl:pl-2"> <CardLayout>
<template #header> <template #header>
<div class="flex justify-between gap-2"> <div class="flex justify-between gap-2">
<div class="flex items-center justify-start gap-2"> <div class="flex items-center justify-start gap-2">
@@ -63,12 +63,12 @@ const handleAction = ({ action, value }) => {
</span> </span>
<span <span
v-if="isDefault" v-if="isDefault"
class="bg-slate-100 dark:bg-slate-800 h-6 inline-flex items-center justify-center rounded-md text-xs border-px border-transparent text-woot-500 dark:text-woot-400 px-2 py-0.5" class="bg-n-alpha-2 h-6 inline-flex items-center justify-center rounded-md text-xs border-px border-transparent text-n-blue-text px-2 py-0.5"
> >
{{ $t('HELP_CENTER.LOCALES_PAGE.LOCALE_CARD.DEFAULT') }} {{ $t('HELP_CENTER.LOCALES_PAGE.LOCALE_CARD.DEFAULT') }}
</span> </span>
</div> </div>
<div class="flex items-center justify-end gap-2"> <div class="flex items-center justify-end gap-4">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<span <span
class="text-sm text-slate-500 dark:text-slate-400 whitespace-nowrap" class="text-sm text-slate-500 dark:text-slate-400 whitespace-nowrap"
@@ -95,10 +95,10 @@ const handleAction = ({ action, value }) => {
<div class="relative group"> <div class="relative group">
<OnClickOutside @trigger="showDropdownMenu = false"> <OnClickOutside @trigger="showDropdownMenu = false">
<Button <Button
variant="ghost" icon="i-lucide-ellipsis-vertical"
size="sm" color="slate"
icon="more-vertical" size="xs"
class="w-8 group-hover:bg-slate-100 dark:group-hover:bg-slate-800" class="rounded-md group-hover:bg-n-solid-2"
@click="showDropdownMenu = !showDropdownMenu" @click="showDropdownMenu = !showDropdownMenu"
/> />

View File

@@ -61,9 +61,9 @@ const agentList = computed(() => {
label: name, label: name,
value: id, value: id,
thumbnail: { name, src: thumbnail }, thumbnail: { name, src: thumbnail },
isSelected: isSelected: props.article?.author?.id
id === props.article?.author?.id || ? id === props.article.author.id
id === (selectedAuthorId.value || currentUserId.value), : id === (selectedAuthorId.value || currentUserId.value),
action: 'assignAuthor', action: 'assignAuthor',
})) }))
// Sort the list by isSelected first, then by name(label) // Sort the list by isSelected first, then by name(label)
@@ -181,20 +181,23 @@ onMounted(() => {
<div class="relative flex items-center gap-2"> <div class="relative flex items-center gap-2">
<OnClickOutside @trigger="openAgentsList = false"> <OnClickOutside @trigger="openAgentsList = false">
<Button <Button
:label="authorName"
variant="ghost" variant="ghost"
class="!px-0 font-normal" class="!px-0 font-normal hover:!bg-transparent"
text-variant="info" text-variant="info"
@click="openAgentsList = !openAgentsList" @click="openAgentsList = !openAgentsList"
> >
<template #leftPrefix> <Thumbnail
<Thumbnail :author="author"
:author="author" :name="authorName"
:name="authorName" :size="20"
:size="20" :src="authorThumbnailSrc"
:src="authorThumbnailSrc" />
/> <span
</template> v-if="author"
class="text-sm text-n-slate-12 hover:text-n-slate-11"
>
{{ author.available_name }}
</span>
</Button> </Button>
<DropdownMenu <DropdownMenu
v-if="openAgentsList && hasAgentList" v-if="openAgentsList && hasAgentList"
@@ -212,13 +215,20 @@ onMounted(() => {
selectedCategory?.name || selectedCategory?.name ||
t('HELP_CENTER.EDIT_ARTICLE_PAGE.EDIT_ARTICLE.UNCATEGORIZED') t('HELP_CENTER.EDIT_ARTICLE_PAGE.EDIT_ARTICLE.UNCATEGORIZED')
" "
:emoji="selectedCategory?.icon || ''" :icon="!selectedCategory?.icon ? 'i-lucide-shapes' : ''"
:icon="!selectedCategory?.icon ? 'play-shape' : ''"
variant="ghost" variant="ghost"
class="!px-2 font-normal" class="!px-2 font-normal hover:!bg-transparent"
text-variant="info"
@click="openCategoryList = !openCategoryList" @click="openCategoryList = !openCategoryList"
/> >
<span
v-if="selectedCategory"
class="text-sm text-n-slate-12 hover:text-n-slate-11"
>
{{
`${selectedCategory.icon || ''} ${selectedCategory.name || t('HELP_CENTER.EDIT_ARTICLE_PAGE.EDIT_ARTICLE.UNCATEGORIZED')}`
}}
</span>
</Button>
<DropdownMenu <DropdownMenu
v-if="openCategoryList && hasCategoryMenuItems" v-if="openCategoryList && hasCategoryMenuItems"
:menu-items="categoryList" :menu-items="categoryList"
@@ -235,11 +245,10 @@ onMounted(() => {
:label=" :label="
t('HELP_CENTER.EDIT_ARTICLE_PAGE.EDIT_ARTICLE.MORE_PROPERTIES') t('HELP_CENTER.EDIT_ARTICLE_PAGE.EDIT_ARTICLE.MORE_PROPERTIES')
" "
icon="add" icon="i-lucide-plus"
variant="ghost" variant="ghost"
:disabled="isNewArticle" :disabled="isNewArticle"
text-variant="info" class="!px-2 font-normal hover:!bg-transparent hover:!text-n-slate-11"
class="!px-2 font-normal"
@click="openProperties = !openProperties" @click="openProperties = !openProperties"
/> />
<ArticleEditorProperties <ArticleEditorProperties

View File

@@ -118,11 +118,11 @@ const updateArticleStatus = async ({ value }) => {
<div class="flex items-center justify-between h-20"> <div class="flex items-center justify-between h-20">
<Button <Button
:label="t('HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.BACK_TO_ARTICLES')" :label="t('HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.BACK_TO_ARTICLES')"
icon="chevron-lucide-left" icon="i-lucide-chevron-left"
icon-lib="lucide"
variant="link" variant="link"
text-variant="info" color="slate"
size="sm" size="sm"
class="ltr:pl-3 rtl:pr-3"
@click="onClickGoBack" @click="onClickGoBack"
/> />
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
@@ -135,7 +135,7 @@ const updateArticleStatus = async ({ value }) => {
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Button <Button
:label="t('HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.PREVIEW')" :label="t('HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.PREVIEW')"
variant="secondary" color="slate"
size="sm" size="sm"
:disabled="!articleId" :disabled="!articleId"
@click="previewArticle" @click="previewArticle"
@@ -156,8 +156,7 @@ const updateArticleStatus = async ({ value }) => {
<div class="relative"> <div class="relative">
<OnClickOutside @trigger="showArticleActionMenu = false"> <OnClickOutside @trigger="showArticleActionMenu = false">
<Button <Button
icon="chevron-lucide-down" icon="i-lucide-chevron-down"
icon-lib="lucide"
size="sm" size="sm"
:disabled="!articleId" :disabled="!articleId"
class="ltr:rounded-l-none rtl:rounded-r-none" class="ltr:rounded-l-none rtl:rounded-r-none"

View File

@@ -63,10 +63,10 @@ onMounted(() => {
}} }}
</h3> </h3>
<Button <Button
icon="dismiss" icon="i-lucide-x"
size="sm" size="sm"
variant="ghost" variant="ghost"
class="w-8 hover:text-n-slate-11" class="hover:text-n-slate-11"
@click="emit('close')" @click="emit('close')"
/> />
</div> </div>
@@ -89,7 +89,7 @@ onMounted(() => {
'HELP_CENTER.EDIT_ARTICLE_PAGE.ARTICLE_PROPERTIES.META_DESCRIPTION_PLACEHOLDER' 'HELP_CENTER.EDIT_ARTICLE_PAGE.ARTICLE_PROPERTIES.META_DESCRIPTION_PLACEHOLDER'
) )
" "
class="w-[224px]" class="w-[220px]"
custom-text-area-wrapper-class="!p-0 !border-0 !rounded-none !bg-transparent transition-none" custom-text-area-wrapper-class="!p-0 !border-0 !rounded-none !bg-transparent transition-none"
custom-text-area-class="max-h-[150px]" custom-text-area-class="max-h-[150px]"
auto-height auto-height

View File

@@ -148,10 +148,9 @@ const handleTabChange = value => {
<Button <Button
:label="activeLocaleName" :label="activeLocaleName"
size="sm" size="sm"
icon-position="right" icon="i-lucide-chevron-down"
icon="chevron-lucide-down" color="slate"
icon-lib="lucide" trailing-icon
variant="secondary"
@click="isLocaleMenuOpen = !isLocaleMenuOpen" @click="isLocaleMenuOpen = !isLocaleMenuOpen"
/> />
@@ -167,11 +166,10 @@ const handleTabChange = value => {
<OnClickOutside @trigger="isCategoryMenuOpen = false"> <OnClickOutside @trigger="isCategoryMenuOpen = false">
<Button <Button
:label="activeCategoryName" :label="activeCategoryName"
icon="i-lucide-chevron-down"
size="sm" size="sm"
icon-position="right" color="slate"
icon="chevron-lucide-down" trailing-icon
icon-lib="lucide"
variant="secondary"
class="max-w-48" class="max-w-48"
@click="isCategoryMenuOpen = !isCategoryMenuOpen" @click="isCategoryMenuOpen = !isCategoryMenuOpen"
/> />
@@ -187,7 +185,7 @@ const handleTabChange = value => {
</div> </div>
<Button <Button
:label="t('HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.NEW_ARTICLE')" :label="t('HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.NEW_ARTICLE')"
icon="add" icon="i-lucide-plus"
size="sm" size="sm"
@click="handleNewArticle" @click="handleNewArticle"
/> />

View File

@@ -198,10 +198,10 @@ defineExpose({ state, isSubmitDisabled });
<OnClickOutside @trigger="isEmojiPickerOpen = false"> <OnClickOutside @trigger="isEmojiPickerOpen = false">
<Button <Button
:label="state.icon" :label="state.icon"
variant="secondary" color="slate"
size="sm" size="sm"
:icon="!state.icon ? 'emoji-add' : ''" :icon="!state.icon ? 'i-lucide-smile-plus' : ''"
class="!h-[38px] !w-[38px] absolute top-[31px] !rounded-[7px] border-0 ltr:left-px rtl:right-px ltr:!rounded-r-none rtl:!rounded-l-none" class="!h-[38px] !w-[38px] absolute top-[31px] !outline-none !rounded-[7px] border-0 ltr:left-px rtl:right-px ltr:!rounded-r-none rtl:!rounded-l-none"
@click="isEmojiPickerOpen = !isEmojiPickerOpen" @click="isEmojiPickerOpen = !isEmojiPickerOpen"
/> />
<EmojiInput <EmojiInput
@@ -243,10 +243,10 @@ defineExpose({ state, isSubmitDisabled });
class="flex items-center justify-between w-full gap-3" class="flex items-center justify-between w-full gap-3"
> >
<Button <Button
variant="ghost" variant="faded"
color="slate"
:label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_DIALOG.BUTTONS.CANCEL')" :label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_DIALOG.BUTTONS.CANCEL')"
text-variant="default" class="w-full bg-n-alpha-2 n-blue-text hover:bg-n-alpha-3"
class="w-full bg-n-alpha-2 hover:bg-n-alpha-3"
@click="handleCancel" @click="handleCancel"
/> />
<Button <Button

View File

@@ -133,10 +133,9 @@ const handleBreadcrumbClick = () => {
<Button <Button
:label="activeLocaleName" :label="activeLocaleName"
size="sm" size="sm"
icon-position="right" trailing-icon
icon="chevron-lucide-down" icon="i-lucide-chevron-down"
icon-lib="lucide" color="slate"
variant="secondary"
@click="isLocaleMenuOpen = !isLocaleMenuOpen" @click="isLocaleMenuOpen = !isLocaleMenuOpen"
/> />
<DropdownMenu <DropdownMenu
@@ -165,7 +164,7 @@ const handleBreadcrumbClick = () => {
<OnClickOutside @trigger="isCreateCategoryDialogOpen = false"> <OnClickOutside @trigger="isCreateCategoryDialogOpen = false">
<Button <Button
:label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.NEW_CATEGORY')" :label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.NEW_CATEGORY')"
icon="add" icon="i-lucide-plus"
size="sm" size="sm"
@click="isCreateCategoryDialogOpen = !isCreateCategoryDialogOpen" @click="isCreateCategoryDialogOpen = !isCreateCategoryDialogOpen"
/> />
@@ -183,7 +182,7 @@ const handleBreadcrumbClick = () => {
<OnClickOutside @trigger="isEditCategoryDialogOpen = false"> <OnClickOutside @trigger="isEditCategoryDialogOpen = false">
<Button <Button
:label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.EDIT_CATEGORY')" :label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_HEADER.EDIT_CATEGORY')"
variant="secondary" color="slate"
size="sm" size="sm"
@click="isEditCategoryDialogOpen = !isEditCategoryDialogOpen" @click="isEditCategoryDialogOpen = !isEditCategoryDialogOpen"
/> />

View File

@@ -41,7 +41,7 @@ const localeCount = computed(() => props.locales?.length);
</div> </div>
<Button <Button
:label="$t('HELP_CENTER.LOCALES_PAGE.NEW_LOCALE_BUTTON_TEXT')" :label="$t('HELP_CENTER.LOCALES_PAGE.NEW_LOCALE_BUTTON_TEXT')"
icon="add" icon="i-lucide-plus"
size="sm" size="sm"
@click="openAddLocaleDialog" @click="openAddLocaleDialog"
/> />

View File

@@ -196,16 +196,17 @@ const handleAvatarDelete = () => {
/> />
</div> </div>
<div class="flex flex-col w-full gap-4"> <div class="flex flex-col w-full gap-4">
<div class="flex items-start justify-between w-full gap-2"> <div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label <label
class="text-sm font-medium whitespace-nowrap min-w-[100px] py-2.5 text-slate-900 dark:text-slate-50" class="text-sm font-medium whitespace-nowrap py-2.5 text-slate-900 dark:text-slate-50"
> >
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.NAME.LABEL') }} {{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.NAME.LABEL') }}
</label> </label>
<Input <Input
v-model="state.name" v-model="state.name"
:placeholder="t('HELP_CENTER.PORTAL_SETTINGS.FORM.NAME.PLACEHOLDER')" :placeholder="t('HELP_CENTER.PORTAL_SETTINGS.FORM.NAME.PLACEHOLDER')"
class="w-[432px]"
:message-type="nameError ? 'error' : 'info'" :message-type="nameError ? 'error' : 'info'"
:message="nameError" :message="nameError"
custom-input-class="!bg-transparent dark:!bg-transparent" custom-input-class="!bg-transparent dark:!bg-transparent"
@@ -213,9 +214,11 @@ const handleAvatarDelete = () => {
@blur="v$.name.$touch()" @blur="v$.name.$touch()"
/> />
</div> </div>
<div class="flex items-start justify-between w-full gap-2"> <div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label <label
class="text-sm font-medium whitespace-nowrap min-w-[100px] py-2.5 text-slate-900 dark:text-slate-50" class="text-sm font-medium whitespace-nowrap py-2.5 text-slate-900 dark:text-slate-50"
> >
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.HEADER_TEXT.LABEL') }} {{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.HEADER_TEXT.LABEL') }}
</label> </label>
@@ -224,13 +227,14 @@ const handleAvatarDelete = () => {
:placeholder=" :placeholder="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.HEADER_TEXT.PLACEHOLDER') t('HELP_CENTER.PORTAL_SETTINGS.FORM.HEADER_TEXT.PLACEHOLDER')
" "
class="w-[432px]"
custom-input-class="!bg-transparent dark:!bg-transparent" custom-input-class="!bg-transparent dark:!bg-transparent"
/> />
</div> </div>
<div class="flex items-start justify-between w-full gap-2"> <div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label <label
class="text-sm font-medium whitespace-nowrap min-w-[100px] text-slate-900 py-2.5 dark:text-slate-50" class="text-sm font-medium whitespace-nowrap text-slate-900 py-2.5 dark:text-slate-50"
> >
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.PAGE_TITLE.LABEL') }} {{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.PAGE_TITLE.LABEL') }}
</label> </label>
@@ -239,13 +243,14 @@ const handleAvatarDelete = () => {
:placeholder=" :placeholder="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.PAGE_TITLE.PLACEHOLDER') t('HELP_CENTER.PORTAL_SETTINGS.FORM.PAGE_TITLE.PLACEHOLDER')
" "
class="w-[432px]"
custom-input-class="!bg-transparent dark:!bg-transparent" custom-input-class="!bg-transparent dark:!bg-transparent"
/> />
</div> </div>
<div class="flex items-start justify-between w-full gap-2"> <div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label <label
class="text-sm font-medium whitespace-nowrap min-w-[100px] text-slate-900 py-2.5 dark:text-slate-50" class="text-sm font-medium whitespace-nowrap text-slate-900 py-2.5 dark:text-slate-50"
> >
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.HOME_PAGE_LINK.LABEL') }} {{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.HOME_PAGE_LINK.LABEL') }}
</label> </label>
@@ -254,7 +259,6 @@ const handleAvatarDelete = () => {
:placeholder=" :placeholder="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.HOME_PAGE_LINK.PLACEHOLDER') t('HELP_CENTER.PORTAL_SETTINGS.FORM.HOME_PAGE_LINK.PLACEHOLDER')
" "
class="w-[432px]"
:message-type="homePageLinkError ? 'error' : 'info'" :message-type="homePageLinkError ? 'error' : 'info'"
:message="homePageLinkError" :message="homePageLinkError"
custom-input-class="!bg-transparent dark:!bg-transparent" custom-input-class="!bg-transparent dark:!bg-transparent"
@@ -262,16 +266,17 @@ const handleAvatarDelete = () => {
@blur="v$.homePageLink.$touch()" @blur="v$.homePageLink.$touch()"
/> />
</div> </div>
<div class="flex items-start justify-between w-full gap-2"> <div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label <label
class="text-sm font-medium whitespace-nowrap min-w-[100px] py-2.5 text-slate-900 dark:text-slate-50" class="text-sm font-medium whitespace-nowrap py-2.5 text-slate-900 dark:text-slate-50"
> >
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.SLUG.LABEL') }} {{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.SLUG.LABEL') }}
</label> </label>
<Input <Input
v-model="state.slug" v-model="state.slug"
:placeholder="t('HELP_CENTER.PORTAL_SETTINGS.FORM.SLUG.PLACEHOLDER')" :placeholder="t('HELP_CENTER.PORTAL_SETTINGS.FORM.SLUG.PLACEHOLDER')"
class="w-[432px]"
:message-type="slugError ? 'error' : 'info'" :message-type="slugError ? 'error' : 'info'"
:message="slugError || buildPortalURL(state.slug)" :message="slugError || buildPortalURL(state.slug)"
custom-input-class="!bg-transparent dark:!bg-transparent" custom-input-class="!bg-transparent dark:!bg-transparent"
@@ -279,9 +284,11 @@ const handleAvatarDelete = () => {
@blur="v$.slug.$touch()" @blur="v$.slug.$touch()"
/> />
</div> </div>
<div class="flex items-start justify-between w-full gap-2"> <div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label <label
class="text-sm font-medium whitespace-nowrap min-w-[100px] py-2.5 text-slate-900 dark:text-slate-50" class="text-sm font-medium whitespace-nowrap py-2.5 text-slate-900 dark:text-slate-50"
> >
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.LIVE_CHAT_WIDGET.LABEL') }} {{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.LIVE_CHAT_WIDGET.LABEL') }}
</label> </label>
@@ -294,12 +301,12 @@ const handleAvatarDelete = () => {
:message=" :message="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.LIVE_CHAT_WIDGET.HELP_TEXT') t('HELP_CENTER.PORTAL_SETTINGS.FORM.LIVE_CHAT_WIDGET.HELP_TEXT')
" "
class="[&>button]:w-[432px] !w-[432px]" class="[&>div>button]:!outline-n-weak"
/> />
</div> </div>
<div class="flex items-start justify-between w-full gap-2"> <div class="flex items-start justify-between w-full gap-2">
<label <label
class="text-sm font-medium whitespace-nowrap min-w-[100px] py-2.5 text-slate-900 dark:text-slate-50" class="text-sm font-medium whitespace-nowrap py-2.5 text-slate-900 dark:text-slate-50"
> >
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.BRAND_COLOR.LABEL') }} {{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.BRAND_COLOR.LABEL') }}
</label> </label>

View File

@@ -82,12 +82,12 @@ const closeDNSConfigurationDialog = () => {
<div class="flex items-center justify-end w-full"> <div class="flex items-center justify-end w-full">
<Button <Button
v-if="customDomainAddress" v-if="customDomainAddress"
color="slate"
:label=" :label="
t( t(
'HELP_CENTER.PORTAL_SETTINGS.CONFIGURATION_FORM.CUSTOM_DOMAIN.EDIT_BUTTON' 'HELP_CENTER.PORTAL_SETTINGS.CONFIGURATION_FORM.CUSTOM_DOMAIN.EDIT_BUTTON'
) )
" "
variant="secondary"
@click="addCustomDomainDialogRef.dialogRef.open()" @click="addCustomDomainDialogRef.dialogRef.open()"
/> />
<Button <Button
@@ -97,7 +97,7 @@ const closeDNSConfigurationDialog = () => {
'HELP_CENTER.PORTAL_SETTINGS.CONFIGURATION_FORM.CUSTOM_DOMAIN.ADD_BUTTON' 'HELP_CENTER.PORTAL_SETTINGS.CONFIGURATION_FORM.CUSTOM_DOMAIN.ADD_BUTTON'
) )
" "
variant="secondary" color="slate"
@click="addCustomDomainDialogRef.dialogRef.open()" @click="addCustomDomainDialogRef.dialogRef.open()"
/> />
</div> </div>

View File

@@ -114,8 +114,8 @@ const handleDeletePortal = () => {
} }
) )
" "
variant="destructive" color="ruby"
class="w-56" class="max-w-56 !w-fit"
@click="openConfirmDeletePortalDialog" @click="openConfirmDeletePortalDialog"
/> />
</div> </div>

View File

@@ -120,8 +120,8 @@ const redirectToPortalHomePage = () => {
</div> </div>
<Button <Button
:label="t('HELP_CENTER.PORTAL_SWITCHER.NEW_PORTAL')" :label="t('HELP_CENTER.PORTAL_SWITCHER.NEW_PORTAL')"
variant="secondary" color="slate"
icon="add" icon="i-lucide-plus"
size="sm" size="sm"
class="!bg-n-alpha-2 hover:!bg-n-alpha-3" class="!bg-n-alpha-2 hover:!bg-n-alpha-3"
@click="openCreatePortalDialog" @click="openCreatePortalDialog"
@@ -133,29 +133,30 @@ const redirectToPortalHomePage = () => {
:key="index" :key="index"
:label="portal.name" :label="portal.name"
variant="ghost" variant="ghost"
:icon="isPortalActive(portal) ? 'checkmark-lucide' : ''" trailing-icon
icon-lib="lucide" :icon="isPortalActive(portal) ? 'i-lucide-check' : ''"
icon-position="right" class="!justify-end !px-2 !py-2 hover:!bg-n-alpha-2 [&>.i-lucide-check]:text-n-teal-10 h-9"
class="!justify-start !px-2 !py-2 hover:!bg-n-alpha-2 [&>svg]:text-n-teal-10 [&>svg]:w-5 [&>svg]:h-5 h-9"
size="sm" size="sm"
@click="handlePortalChange(portal)" @click="handlePortalChange(portal)"
> >
<template #leftPrefix> <div v-if="portal.custom_domain" class="flex items-center gap-1">
<Thumbnail <span class="i-lucide-link size-3" />
v-if="portal"
:author="portal"
:name="portal.name"
:size="20"
:src="getPortalThumbnailSrc(portal)"
:show-author-name="false"
icon-name="building-lucide"
/>
</template>
<template #rightPrefix>
<span class="text-sm truncate text-n-slate-11"> <span class="text-sm truncate text-n-slate-11">
{{ portal.custom_domain || '' }} {{ portal.custom_domain || '' }}
</span> </span>
</template> </div>
<span class="text-sm font-medium truncate text-n-slate-12">
{{ portal.name || '' }}
</span>
<Thumbnail
v-if="portal"
:author="portal"
:name="portal.name"
:size="20"
:src="getPortalThumbnailSrc(portal)"
:show-author-name="false"
icon-name="i-lucide-building-2"
/>
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue'; import Icon from 'dashboard/components-next/icon/Icon.vue';
const props = defineProps({ const props = defineProps({
src: { src: {
@@ -57,7 +57,7 @@ const handleDismiss = event => {
<template> <template>
<div <div
class="relative flex flex-col items-center gap-2 select-none rounded-xl group/avatar" class="relative flex flex-col items-center gap-2 select-none rounded-xl outline outline-1 outline-n-container group/avatar"
:style="{ width: avatarSize, height: avatarSize }" :style="{ width: avatarSize, height: avatarSize }"
> >
<img <img
@@ -71,29 +71,27 @@ const handleDismiss = event => {
v-else v-else
class="flex items-center justify-center w-full h-full rounded-xl bg-n-alpha-2" class="flex items-center justify-center w-full h-full rounded-xl bg-n-alpha-2"
> >
<FluentIcon <Icon
icon="building-lucide" icon="i-lucide-building-2"
icon-lib="lucide" class="text-n-brand/50"
:size="iconSize" :style="{ width: `${iconSize}`, height: `${iconSize}` }"
class="dark:text-n-brand/50 text-n-brand/30"
/> />
</div> </div>
<div <div
v-if="src" v-if="src"
class="absolute z-20 flex items-center cursor-pointer justify-center w-6 h-6 transition-all invisible opacity-0 duration-500 ease-in-out -top-2.5 -right-2.5 rounded-xl bg-n-solid-3 group-hover/avatar:visible group-hover/avatar:opacity-100" class="absolute z-20 outline outline-1 outline-n-container flex items-center cursor-pointer justify-center w-6 h-6 transition-all invisible opacity-0 duration-500 ease-in-out -top-2.5 -right-2.5 rounded-xl bg-n-solid-3 group-hover/avatar:visible group-hover/avatar:opacity-100"
@click="handleDismiss" @click="handleDismiss"
> >
<FluentIcon icon="dismiss" :size="16" class="text-n-slate-11" /> <Icon icon="i-lucide-x" class="text-n-slate-11 size-4" />
</div> </div>
<div <div
class="absolute inset-0 z-10 flex items-center justify-center invisible w-full h-full transition-all duration-500 ease-in-out opacity-0 rounded-xl bg-n-alpha-black1 group-hover/avatar:visible group-hover/avatar:opacity-100" class="absolute inset-0 z-10 flex items-center justify-center invisible w-full h-full transition-all duration-500 ease-in-out opacity-0 rounded-xl bg-n-alpha-black1 group-hover/avatar:visible group-hover/avatar:opacity-100"
@click="handleUploadAvatar" @click="handleUploadAvatar"
> >
<FluentIcon <Icon
icon="upload-lucide" icon="i-lucide-upload"
icon-lib="lucide" class="text-white"
:size="iconSize" :style="{ width: `${iconSize}`, height: `${iconSize}` }"
class="text-white dark:text-white"
/> />
<input <input
ref="fileInput" ref="fileInput"

View File

@@ -2,8 +2,7 @@
import { defineProps } from 'vue'; import { defineProps } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from 'dashboard/components-next/button/Button.vue'; import Icon from 'dashboard/components-next/icon/Icon.vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
defineProps({ defineProps({
items: { items: {
@@ -33,21 +32,17 @@ const onClick = event => {
<nav :aria-label="t('BREADCRUMB.ARIA_LABEL')" class="flex items-center h-8"> <nav :aria-label="t('BREADCRUMB.ARIA_LABEL')" class="flex items-center h-8">
<ol class="flex items-center mb-0"> <ol class="flex items-center mb-0">
<li v-for="(item, index) in items" :key="index" class="flex items-center"> <li v-for="(item, index) in items" :key="index" class="flex items-center">
<Button <button
v-if="index === 0" v-if="index === 0"
:label="item.label" class="inline-flex items-center justify-center min-w-0 gap-2 p-0 text-sm font-medium transition-all duration-200 ease-in-out border-0 rounded-lg text-n-slate-11 hover:text-n-slate-12 outline-transparent max-w-56"
variant="link"
text-variant="info"
class="!p-0 text-sm !font-normal hover:!no-underline max-w-56 !text-slate-300 dark:!text-slate-500 hover:!text-slate-700 dark:hover:!text-slate-100"
size="sm"
@click="onClick" @click="onClick"
/> >
<span class="min-w-0 truncate">{{ item.label }}</span>
</button>
<template v-else> <template v-else>
<FluentIcon <Icon
icon="chevron-lucide-right" icon="i-lucide-chevron-right"
size="18" class="flex-shrink-0 mx-2 size-4 text-n-slate-11 dark:text-n-slate-11"
icon-lib="lucide"
class="flex-shrink-0 mx-2 text-slate-300 dark:text-slate-500"
/> />
<span <span
class="text-sm truncate text-slate-900 dark:text-slate-50 max-w-56" class="text-sm truncate text-slate-900 dark:text-slate-50 max-w-56"

View File

@@ -1,125 +1,123 @@
<script setup> <script setup>
import Button from './Button.vue'; import Button from './Button.vue';
// Constants for documentation
const VARIANTS = ['solid', 'outline', 'faded', 'link', 'ghost'];
const COLORS = ['blue', 'ruby', 'amber', 'slate', 'teal'];
const SIZES = ['default', 'sm', 'lg'];
</script> </script>
<template> <template>
<Story title="Components/Button" :layout="{ type: 'grid', width: '400' }"> <Story title="Components/Button" :layout="{ type: 'grid', width: '800px' }">
<Variant title="Default"> <!-- Basic Variants -->
<div class="p-4 bg-white dark:bg-slate-900"> <Variant title="Basic Variants">
<Button label="Default Button" /> <div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button
v-for="variant in VARIANTS"
:key="variant"
:label="variant"
:variant="variant"
/>
</div> </div>
</Variant> </Variant>
<Variant title="Disabled"> <!-- Colors -->
<Variant title="Color Variants">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button
v-for="color in COLORS"
:key="color"
:label="color"
:color="color"
/>
</div>
</Variant>
<!-- Sizes -->
<Variant title="Size Variants">
<div
class="flex flex-wrap items-center gap-2 p-4 bg-white dark:bg-slate-900"
>
<Button v-for="size in SIZES" :key="size" :label="size" :size="size" />
</div>
</Variant>
<!-- Icons -->
<Variant title="Icons">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button label="Leading Icon" icon="i-lucide-plus" />
<Button label="Trailing Icon" icon="i-lucide-plus" trailing-icon />
<Button icon="i-lucide-plus" />
</div>
</Variant>
<!-- Loading State -->
<Variant title="Loading State">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button label="Loading" is-loading />
<Button label="Loading" variant="outline" is-loading />
<Button is-loading icon="i-lucide-plus" />
</div>
</Variant>
<!-- Disabled State -->
<Variant title="Disabled State">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900"> <div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button label="Disabled" disabled /> <Button label="Disabled" disabled />
<Button label="Disabled" variant="outline" disabled /> <Button label="Disabled Outline" variant="outline" disabled />
<Button label="Disabled" disabled icon="delete" variant="outline" /> <Button label="Disabled Icon" icon="delete" disabled />
<Button <Button
label="Disabled" label="Disabled Destructive"
color="ruby"
disabled disabled
icon="delete" icon="delete"
variant="destructive"
size="sm"
/>
<Button
label="Disabled"
disabled
icon="delete"
variant="ghost"
size="sm"
/>
<Button
label="Disabled"
disabled
icon="delete"
variant="link"
size="sm" size="sm"
/> />
</div> </div>
</Variant> </Variant>
<Variant title="Disabled with icon"> <!-- Color Combinations -->
<div class="p-4 bg-white dark:bg-slate-900"> <Variant title="Color & Variant Combinations">
<Button label="Disabled Button" icon="emoji-add" disabled /> <div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<template v-for="color in COLORS" :key="color">
<Button
v-for="variant in VARIANTS"
:key="`${color}-${variant}`"
:label="`${color} ${variant}`"
:color="color"
:variant="variant"
/>
</template>
</div> </div>
</Variant> </Variant>
<Variant title="Different variant"> <!-- Icon Positions -->
<Variant title="Icon Positions & Sizes">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900"> <div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button label="Default" variant="default" /> <template v-for="size in SIZES" :key="size">
<Button label="Destructive" variant="destructive" /> <Button
<Button label="Outline" variant="outline" /> :label="`${size} Leading`"
<Button label="Secondary" variant="secondary" /> icon="i-lucide-plus"
<Button label="Ghost" variant="ghost" /> :size="size"
<Button label="Link" variant="link" /> />
<Button
:label="`${size} Trailing`"
icon="i-lucide-plus"
trailing-icon
:size="size"
/>
<Button icon="i-lucide-plus" :size="size" />
</template>
</div> </div>
</Variant> </Variant>
<Variant title="Different variant with icon only"> <!-- Ghost & Link Variants -->
<Variant title="Ghost & Link Variants">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900"> <div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button icon="emoji-add" variant="default" /> <Button label="Ghost Button" variant="ghost" />
<Button icon="emoji-add" variant="destructive" /> <Button label="Ghost with Icon" variant="ghost" icon="i-lucide-plus" />
<Button icon="emoji-add" variant="outline" /> <Button label="Link Button" variant="link" />
<Button icon="emoji-add" variant="secondary" /> <Button label="Link with Icon" variant="link" icon="i-lucide-plus" />
<Button icon="emoji-add" variant="ghost" />
<Button icon="emoji-add" variant="link" />
</div>
</Variant>
<Variant title="Different size">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button label="Default" />
<Button label="Large" size="lg" />
<Button label="Small" size="sm" />
</div>
</Variant>
<Variant title="Different text variant">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button label="Default" text-variant="default" variant="outline" />
<Button label="Success" text-variant="success" variant="outline" />
<Button label="Warning" text-variant="warning" variant="outline" />
<Button label="Danger" text-variant="danger" variant="outline" />
<Button label="Info" text-variant="info" variant="outline" />
</div>
</Variant>
<Variant title="Button with left icon with different sizes and icon only">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button label="Default" icon="emoji-add" icon-position="left" />
<Button
label="Default LG"
icon="emoji-add"
icon-position="left"
size="lg"
/>
<Button
label="Default SM"
icon="emoji-add"
icon-position="left"
size="sm"
/>
<Button icon="emoji-add" size="sm" />
</div>
</Variant>
<Variant title="Button with right icon with different sizes and icon only">
<div class="flex flex-wrap gap-2 p-4 bg-white dark:bg-slate-900">
<Button label="Default" icon="emoji-add" icon-position="right" />
<Button
label="Default LG"
icon="emoji-add"
icon-position="right"
size="lg"
/>
<Button
label="Default SM"
icon="emoji-add"
icon-position="right"
size="sm"
/>
<Button icon="emoji-add" size="sm" />
</div> </div>
</Variant> </Variant>
</Story> </Story>

View File

@@ -1,8 +1,8 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed, useSlots } from 'vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue'; import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
const props = defineProps({ const props = defineProps({
label: { label: {
@@ -11,49 +11,28 @@ const props = defineProps({
}, },
variant: { variant: {
type: String, type: String,
default: 'default', default: 'solid',
validator: value => validator: value =>
[ ['solid', 'outline', 'faded', 'link', 'ghost'].includes(value),
'default',
'destructive',
'outline',
'secondary',
'ghost',
'link',
].includes(value),
}, },
textVariant: { color: {
type: String, type: String,
default: '', default: 'blue',
validator: value => validator: value =>
['', 'default', 'success', 'warning', 'danger', 'info'].includes(value), ['blue', 'ruby', 'amber', 'slate', 'teal'].includes(value),
}, },
size: { size: {
type: String, type: String,
default: 'default', default: 'md',
validator: value => ['default', 'sm', 'lg'].includes(value), validator: value => ['xs', 'sm', 'md', 'lg'].includes(value),
},
type: {
type: String,
default: 'button',
validator: value => ['button', 'submit', 'reset'].includes(value),
}, },
icon: { icon: {
type: String, type: String,
default: '', default: '',
}, },
emoji: { trailingIcon: {
type: String, type: Boolean,
default: '', default: false,
},
iconPosition: {
type: String,
default: 'left',
validator: value => ['left', 'right'].includes(value),
},
iconLib: {
type: String,
default: 'fluent',
}, },
isLoading: { isLoading: {
type: Boolean, type: Boolean,
@@ -61,92 +40,128 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(['click']); const slots = useSlots();
const buttonVariants = { const STYLE_CONFIG = {
variant: { colors: {
default: blue: {
'bg-n-brand text-white dark:text-white hover:bg-woot-600 dark:hover:bg-woot-600', solid: 'bg-n-brand text-white hover:bg-n-blue-text outline-transparent',
destructive: 'bg-n-ruby-9 text-white dark:text-white hover:bg-n-ruby-10', faded:
outline: 'bg-n-brand/10 text-n-slate-12 hover:bg-n-brand/20 outline-transparent',
'border border-n-weak dark:border-n-weak hover:border-n-slate-6 dark:hover:border-n-slate-6', outline: 'text-n-blue-text hover:bg-n-brand/10 outline-n-blue-border',
secondary: 'bg-n-solid-3 text-n-slate-12 hover:bg-n-solid-2', link: 'text-n-brand hover:underline outline-transparent',
ghost: 'text-n-slate-12', },
link: 'text-n-brand underline-offset-4 hover:underline dark:hover:underline', ruby: {
solid: 'bg-n-ruby-9 text-white hover:bg-n-ruby-10 outline-transparent',
faded:
'bg-n-ruby-9/10 text-n-slate-12 hover:bg-n-ruby-9/20 outline-transparent',
outline: 'text-n-ruby-11 hover:bg-n-ruby-9/10 outline-n-ruby-9',
link: 'text-n-ruby-9 hover:underline outline-transparent',
},
amber: {
solid: 'bg-n-amber-9 text-white hover:bg-n-amber-10 outline-transparent',
faded:
'bg-n-amber-9/10 text-n-slate-12 hover:bg-n-amber-9/20 outline-transparent',
outline: 'text-n-amber-11 hover:bg-n-amber-9/10 outline-n-amber-9',
link: 'text-n-amber-9 hover:underline outline-transparent',
},
slate: {
solid:
'bg-n-solid-3 hover:bg-n-solid-2 text-n-slate-12 outline-n-container',
faded:
'bg-n-slate-9/10 text-n-slate-12 hover:bg-n-slate-9/20 outline-transparent',
outline: 'text-n-slate-11 outline-n-strong hover:bg-n-slate-9/10',
link: 'text-n-slate-11 hover:text-n-slate-12 hover:underline outline-transparent',
},
teal: {
solid: 'bg-n-teal-9 text-white hover:bg-n-teal-10 outline-transparent',
faded:
'bg-n-teal-9/10 text-n-slate-12 hover:bg-n-teal-9/20 outline-transparent',
outline: 'text-n-teal-11 hover:bg-n-teal-9/10 outline-n-teal-9',
link: 'text-n-teal-9 hover:underline outline-transparent',
},
}, },
size: { sizes: {
default: 'h-10 px-4 py-2', regular: {
sm: 'h-8 px-3 py-1', xs: 'h-6 px-2',
lg: 'h-12 px-5 py-3', sm: 'h-8 px-3',
md: 'h-10 px-4',
lg: 'h-12 px-5',
},
iconOnly: {
xs: 'h-6 w-6 p-0',
sm: 'h-8 w-8 p-0',
md: 'h-10 w-10 p-0',
lg: 'h-12 w-12 p-0',
},
link: {
xs: 'p-0',
sm: 'p-0',
md: 'p-0',
lg: 'p-0',
},
}, },
text: { fontSize: {
default: xs: 'text-xs',
'!text-n-brand dark:!text-n-brand hover:!text-woot-600 dark:hover:!text-woot-600', sm: 'text-sm',
success: md: 'text-sm font-medium',
'!text-green-500 dark:!text-green-500 hover:!text-green-600 dark:hover:!text-green-600', lg: 'text-base',
warning:
'!text-amber-600 dark:!text-amber-600 hover:!text-amber-600 dark:hover:!text-amber-600',
danger: '!text-n-ruby-11 hover:!text-n-ruby-10',
info: '!text-n-slate-12 hover:!text-n-slate-11',
}, },
base: 'inline-flex items-center justify-center min-w-0 gap-2 transition-all duration-200 ease-in-out border-0 rounded-lg outline-1 outline disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50',
}; };
const buttonClasses = computed(() => { const variantClasses = computed(() => {
const classes = [ const variantMap = {
buttonVariants.variant[props.variant], ghost: 'text-n-slate-12 hover:bg-n-alpha-2 outline-transparent',
buttonVariants.size[props.size], link: `${STYLE_CONFIG.colors[props.color].link} p-0 font-medium underline-offset-4`,
]; outline: STYLE_CONFIG.colors[props.color].outline,
faded: STYLE_CONFIG.colors[props.color].faded,
solid: STYLE_CONFIG.colors[props.color].solid,
};
if (props.textVariant && buttonVariants.text[props.textVariant]) { return variantMap[props.variant];
classes.push(buttonVariants.text[props.textVariant]); });
}
const isIconOnly = computed(() => !props.label && !slots.default);
const isLink = computed(() => props.variant === 'link');
const buttonClasses = computed(() => {
const sizeConfig = isIconOnly.value ? 'iconOnly' : 'regular';
const classes = [
variantClasses.value,
props.variant !== 'link' && STYLE_CONFIG.sizes[sizeConfig][props.size],
].filter(Boolean);
return classes.join(' '); return classes.join(' ');
}); });
const iconSize = computed(() => { const linkButtonClasses = computed(() => {
if (props.size === 'sm') return 16; const classes = [
if (props.size === 'lg') return 20; variantClasses.value,
return 18; STYLE_CONFIG.sizes.link[props.size],
}); ].filter(Boolean);
const handleClick = e => { return classes.join(' ');
emit('click', e); });
};
</script> </script>
<template> <template>
<button <button
:class="buttonClasses" :class="{
:type="type" [STYLE_CONFIG.base]: true,
class="inline-flex items-center justify-center min-w-0 gap-2 text-sm font-medium transition-all duration-200 ease-in-out rounded-lg disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50" [isLink ? linkButtonClasses : buttonClasses]: true,
@click="handleClick" [STYLE_CONFIG.fontSize[size]]: true,
'flex-row-reverse': trailingIcon && !isIconOnly,
}"
> >
<FluentIcon <slot v-if="(icon || $slots.icon) && !isLoading" name="icon">
v-if="icon && iconPosition === 'left' && !isLoading" <Icon :icon="icon" class="flex-shrink-0" />
:icon="icon" </slot>
:size="iconSize"
:icon-lib="iconLib"
class="flex-shrink-0"
:class="{
'text-n-slate-11 dark:text-n-slate-11': variant === 'secondary',
}"
/>
<Spinner v-if="isLoading" class="!w-5 !h-5 flex-shrink-0" /> <Spinner v-if="isLoading" class="!w-5 !h-5 flex-shrink-0" />
<slot name="leftPrefix" />
<span v-if="emoji">{{ emoji }}</span> <slot v-if="label || $slots.default" name="default">
<span v-if="label" class="min-w-0 truncate">{{ label }}</span> <span class="min-w-0 truncate">{{ label }}</span>
<slot /> </slot>
<slot name="rightPrefix" />
<FluentIcon
v-if="icon && iconPosition === 'right'"
:icon="icon"
:size="iconSize"
:icon-lib="iconLib"
class="flex-shrink-0"
:class="{
'text-n-slate-11 dark:text-n-slate-11': variant === 'secondary',
}"
/>
</button> </button>
</template> </template>

View File

@@ -37,20 +37,19 @@ const pickerRef = ref(null);
<div ref="pickerRef" class="relative w-fit"> <div ref="pickerRef" class="relative w-fit">
<OnClickOutside @trigger="closeTogglePicker"> <OnClickOutside @trigger="closeTogglePicker">
<Button <Button
:label="modelValue" color="slate"
variant="secondary" icon="i-lucide-pipette"
icon-lib="lucide" trailing-icon
icon-position="right"
icon="pipette-lucide"
class="!px-3 !py-3 [&>svg]:w-4 [&>svg]:h-4" class="!px-3 !py-3 [&>svg]:w-4 [&>svg]:h-4"
@click="toggleColorPicker" @click="toggleColorPicker"
> >
<template #leftPrefix> <div class="flex items-center flex-grow gap-2">
<div <span
class="w-4 h-4 rounded-sm" class="rounded-md size-4"
:style="{ backgroundColor: modelValue }" :style="{ backgroundColor: modelValue }"
/> />
</template> <span class="min-w-0 truncate">{{ modelValue }}</span>
</div>
</Button> </Button>
<Chrome <Chrome
v-if="isPickerOpen" v-if="isPickerOpen"

View File

@@ -2,7 +2,7 @@
import { ref, computed, watch, nextTick } from 'vue'; import { ref, computed, watch, nextTick } from 'vue';
import { OnClickOutside } from '@vueuse/components'; import { OnClickOutside } from '@vueuse/components';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
const props = defineProps({ const props = defineProps({
@@ -89,7 +89,7 @@ watch(
<template> <template>
<div <div
ref="comboboxRef" ref="comboboxRef"
class="relative w-full" class="relative w-full min-w-0"
:class="{ :class="{
'cursor-not-allowed': disabled, 'cursor-not-allowed': disabled,
'group/combobox': !disabled, 'group/combobox': !disabled,
@@ -98,12 +98,12 @@ watch(
<OnClickOutside @trigger="open = false"> <OnClickOutside @trigger="open = false">
<Button <Button
variant="outline" variant="outline"
color="slate"
:label="selectedLabel" :label="selectedLabel"
icon-position="right" trailing-icon
:disabled="disabled" :disabled="disabled"
class="justify-between w-full !px-2 !py-2.5 text-n-slate-12 font-normal group-hover/combobox:border-n-slate-6" class="justify-between w-full !px-3 !py-2.5 text-n-slate-12 font-normal group-hover/combobox:border-n-slate-6"
:icon="open ? 'chevron-lucide-up' : 'chevron-lucide-down'" :icon="open ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
icon-lib="lucide"
@click="toggleDropdown" @click="toggleDropdown"
/> />
<div <div
@@ -111,12 +111,7 @@ watch(
class="absolute z-50 w-full mt-1 transition-opacity duration-200 border rounded-md shadow-lg bg-n-solid-1 border-n-strong" class="absolute z-50 w-full mt-1 transition-opacity duration-200 border rounded-md shadow-lg bg-n-solid-1 border-n-strong"
> >
<div class="relative border-b border-n-strong"> <div class="relative border-b border-n-strong">
<FluentIcon <span class="absolute i-lucide-search top-2.5 size-4 left-3" />
icon="search"
:size="14"
class="absolute text-gray-400 dark:text-slate-500 top-3 left-3"
aria-hidden="true"
/>
<input <input
ref="searchInput" ref="searchInput"
v-model="search" v-model="search"
@@ -133,7 +128,7 @@ watch(
<li <li
v-for="option in filteredOptions" v-for="option in filteredOptions"
:key="option.value" :key="option.value"
class="flex items-center justify-between !text-n-slate-12 w-full gap-2 px-2 py-2 text-sm transition-colors duration-150 cursor-pointer hover:bg-n-solid-2" class="flex items-center justify-between !text-n-slate-12 w-full gap-2 px-3 py-2 text-sm transition-colors duration-150 cursor-pointer hover:bg-n-solid-2"
:class="{ :class="{
'bg-n-solid-2': option.value === selectedValue, 'bg-n-solid-2': option.value === selectedValue,
}" }"
@@ -144,12 +139,9 @@ watch(
<span :class="{ 'font-medium': option.value === selectedValue }"> <span :class="{ 'font-medium': option.value === selectedValue }">
{{ option.label }} {{ option.label }}
</span> </span>
<FluentIcon <span
v-if="option.value === selectedValue" v-if="option.value === selectedValue"
icon="checkmark" class="flex-shrink-0 i-lucide-check size-4 text-n-slate-11"
:size="16"
class="flex-shrink-0 text-n-slate-11 dark:text-n-slate-11"
aria-hidden="true"
/> />
</li> </li>
<li <li

View File

@@ -99,14 +99,15 @@ defineExpose({ open, close });
<div class="flex items-center justify-between w-full gap-3"> <div class="flex items-center justify-between w-full gap-3">
<Button <Button
v-if="showCancelButton" v-if="showCancelButton"
variant="ghost" variant="faded"
color="slate"
:label="cancelButtonLabel || t('DIALOG.BUTTONS.CANCEL')" :label="cancelButtonLabel || t('DIALOG.BUTTONS.CANCEL')"
class="w-full bg-n-alpha-2 hover:bg-n-alpha-3" class="w-full"
@click="close" @click="close"
/> />
<Button <Button
v-if="showConfirmButton" v-if="showConfirmButton"
:variant="type === 'edit' ? 'default' : 'destructive'" :color="type === 'edit' ? 'blue' : 'ruby'"
:label="confirmButtonLabel || t('DIALOG.BUTTONS.CONFIRM')" :label="confirmButtonLabel || t('DIALOG.BUTTONS.CONFIRM')"
class="w-full" class="w-full"
:is-loading="isLoading" :is-loading="isLoading"

View File

@@ -1,7 +1,7 @@
<script setup> <script setup>
import { defineProps, defineEmits } from 'vue'; import { defineProps, defineEmits } from 'vue';
import Button from 'dashboard/components-next/button/Button.vue'; import Icon from 'dashboard/components-next/icon/Icon.vue';
import Thumbnail from 'dashboard/components-next/thumbnail/Thumbnail.vue'; import Thumbnail from 'dashboard/components-next/thumbnail/Thumbnail.vue';
defineProps({ defineProps({
@@ -29,29 +29,28 @@ const handleAction = (action, value) => {
<div <div
class="bg-n-alpha-3 backdrop-blur-[100px] absolute rounded-xl z-50 py-2 px-2 gap-2 flex flex-col min-w-[136px] shadow-lg" class="bg-n-alpha-3 backdrop-blur-[100px] absolute rounded-xl z-50 py-2 px-2 gap-2 flex flex-col min-w-[136px] shadow-lg"
> >
<Button <button
v-for="item in menuItems" v-for="item in menuItems"
:key="item.action" :key="item.action"
:label="item.label" class="inline-flex items-center justify-start w-full h-8 min-w-0 gap-2 px-2 py-1.5 transition-all duration-200 ease-in-out border-0 rounded-lg z-60 hover:bg-n-alpha-1 dark:hover:bg-n-alpha-2 disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50"
:icon="item.icon" :class="{
:emoji="item.emoji" 'bg-n-alpha-1 dark:bg-n-solid-active': item.isSelected,
'text-n-ruby-11': item.action === 'delete',
'text-n-slate-12': item.action !== 'delete',
}"
:disabled="item.disabled" :disabled="item.disabled"
variant="ghost"
size="sm"
class="!justify-start w-full hover:!bg-n-slate-3 dark:hover:!bg-n-slate-4 z-60 px-2 font-normal"
:class="item.isSelected ? '!bg-n-alpha-1 dark:!bg-n-solid-active' : ''"
:text-variant="item.action === 'delete' ? 'danger' : ''"
@click="handleAction(item.action, item.value)" @click="handleAction(item.action, item.value)"
> >
<template #leftPrefix> <Thumbnail
<Thumbnail v-if="item.thumbnail"
v-if="item.thumbnail" :author="item.thumbnail"
:author="item.thumbnail" :name="item.thumbnail.name"
:name="item.thumbnail.name" :size="thumbnailSize"
:size="thumbnailSize" :src="item.thumbnail.src"
:src="item.thumbnail.src" />
/> <Icon v-if="item.icon" :icon="item.icon" />
</template> <span v-if="item.emoji">{{ item.emoji }}</span>
</Button> <span v-if="item.label" class="text-sm">{{ item.label }}</span>
</button>
</div> </div>
</template> </template>

View File

@@ -69,7 +69,7 @@ const handleInput = event => {
</script> </script>
<template> <template>
<div class="relative flex flex-col gap-1"> <div class="relative flex flex-col min-w-0 gap-1">
<label <label
v-if="label" v-if="label"
:for="id" :for="id"
@@ -92,7 +92,7 @@ const handleInput = event => {
/> />
<p <p
v-if="message" v-if="message"
class="mt-1 mb-0 text-xs truncate transition-all duration-500 ease-in-out" class="min-w-0 mt-1 mb-0 text-xs truncate transition-all duration-500 ease-in-out"
:class="messageClass" :class="messageClass"
> >
{{ message }} {{ message }}

View File

@@ -65,18 +65,18 @@ const pageInfo = computed(() => {
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Button <Button
icon="chevrons-lucide-left" icon="i-lucide-chevrons-left"
icon-lib="lucide"
variant="ghost" variant="ghost"
size="sm" size="sm"
class="!w-8 !h-6"
:disabled="isFirstPage" :disabled="isFirstPage"
@click="changePage(1)" @click="changePage(1)"
/> />
<Button <Button
icon="chevron-lucide-left" icon="i-lucide-chevron-left"
icon-lib="lucide"
variant="ghost" variant="ghost"
size="sm" size="sm"
class="!w-8 !h-6"
:disabled="isFirstPage" :disabled="isFirstPage"
@click="changePage(currentPage - 1)" @click="changePage(currentPage - 1)"
/> />
@@ -87,18 +87,18 @@ const pageInfo = computed(() => {
<span>{{ pageInfo }}</span> <span>{{ pageInfo }}</span>
</div> </div>
<Button <Button
icon="chevron-lucide-right" icon="i-lucide-chevron-right"
icon-lib="lucide"
variant="ghost" variant="ghost"
size="sm" size="sm"
class="!w-8 !h-6"
:disabled="isLastPage" :disabled="isLastPage"
@click="changePage(currentPage + 1)" @click="changePage(currentPage + 1)"
/> />
<Button <Button
icon="chevrons-lucide-right" icon="i-lucide-chevrons-right"
icon-lib="lucide"
variant="ghost" variant="ghost"
size="sm" size="sm"
class="!w-8 !h-6"
:disabled="isLastPage" :disabled="isLastPage"
@click="changePage(totalPages)" @click="changePage(totalPages)"
/> />

View File

@@ -43,7 +43,7 @@ const showDivider = index => {
class="relative px-4 truncate py-1.5 text-sm border-0 outline-1 outline rounded-lg transition-colors duration-300 ease-in-out hover:text-n-brand" class="relative px-4 truncate py-1.5 text-sm border-0 outline-1 outline rounded-lg transition-colors duration-300 ease-in-out hover:text-n-brand"
:class="[ :class="[
activeTab === index activeTab === index
? 'text-n-brand bg-n-solid-active outline-n-container dark:outline-transparent' ? 'text-n-blue-text bg-n-solid-active outline-n-container dark:outline-transparent'
: 'text-n-slate-10 outline-transparent h-8', : 'text-n-slate-10 outline-transparent h-8',
]" ]"
@click="selectTab(index)" @click="selectTab(index)"
@@ -54,7 +54,7 @@ const showDivider = index => {
class="w-px h-3.5 rounded my-auto transition-colors duration-300 ease-in-out" class="w-px h-3.5 rounded my-auto transition-colors duration-300 ease-in-out"
:class=" :class="
showDivider(index) showDivider(index)
? 'bg-slate-75 dark:bg-slate-800' ? 'bg-n-strong'
: 'bg-transparent dark:bg-transparent' : 'bg-transparent dark:bg-transparent'
" "
/> />

View File

@@ -2,7 +2,6 @@
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import { OnClickOutside } from '@vueuse/components'; import { OnClickOutside } from '@vueuse/components';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
import InlineInput from 'dashboard/components-next/inline-input/InlineInput.vue'; import InlineInput from 'dashboard/components-next/inline-input/InlineInput.vue';
const props = defineProps({ const props = defineProps({
@@ -71,10 +70,8 @@ watch(
<span class="flex-grow min-w-0 text-sm truncate text-n-slate-12"> <span class="flex-grow min-w-0 text-sm truncate text-n-slate-12">
{{ tag }} {{ tag }}
</span> </span>
<FluentIcon <span
icon="dismiss" class="flex-shrink-0 cursor-pointer i-lucide-x size-3.5 text-n-slate-11"
size="20"
class="flex-shrink-0 p-1 cursor-pointer text-n-slate-11"
@click.stop="removeTag(index)" @click.stop="removeTag(index)"
/> />
</div> </div>

View File

@@ -3,8 +3,6 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { removeEmoji } from 'shared/helpers/emoji'; import { removeEmoji } from 'shared/helpers/emoji';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
const props = defineProps({ const props = defineProps({
author: { author: {
type: Object, type: Object,
@@ -58,7 +56,7 @@ const fontSize = computed(() => {
}); });
const iconSize = computed(() => { const iconSize = computed(() => {
return props.size / 2; return Math.round(props.size / 1.8);
}); });
const shouldShowImage = computed(() => { const shouldShowImage = computed(() => {
@@ -76,7 +74,7 @@ const onImgLoad = () => {
<template> <template>
<div <div
class="flex items-center justify-center rounded-full bg-slate-100 dark:bg-slate-700/50" class="flex items-center justify-center rounded-full bg-n-slate-3 dark:bg-n-slate-4"
:style="{ width: `${size}px`, height: `${size}px` }" :style="{ width: `${size}px`, height: `${size}px` }"
> >
<div v-if="author"> <div v-if="author">
@@ -91,7 +89,7 @@ const onImgLoad = () => {
<template v-else> <template v-else>
<span <span
v-if="showAuthorName" v-if="showAuthorName"
class="flex items-center justify-center font-medium text-slate-500 dark:text-slate-400" class="flex items-center justify-center font-medium text-n-slate-11"
:style="{ fontSize: `${fontSize}px` }" :style="{ fontSize: `${fontSize}px` }"
> >
{{ authorInitial }} {{ authorInitial }}
@@ -100,12 +98,10 @@ const onImgLoad = () => {
v-else v-else
class="flex items-center justify-center w-full h-full rounded-xl" class="flex items-center justify-center w-full h-full rounded-xl"
> >
<FluentIcon <span
v-if="iconName" v-if="iconName"
:icon="iconName" :class="`${iconName} text-n-brand/70`"
icon-lib="lucide" :style="{ width: `${iconSize}px`, height: `${iconSize}px` }"
:size="iconSize"
class="text-n-brand"
/> />
</div> </div>
</template> </template>
@@ -113,14 +109,9 @@ const onImgLoad = () => {
<div <div
v-else v-else
v-tooltip.top-start="t('THUMBNAIL.AUTHOR.NOT_AVAILABLE')" v-tooltip.top-start="t('THUMBNAIL.AUTHOR.NOT_AVAILABLE')"
class="flex items-center justify-center w-4 h-4 rounded-full bg-slate-100 dark:bg-slate-700/50" class="flex items-center justify-center w-4 h-4 rounded-full bg-n-slate-3 dark:bg-n-slate-4"
> >
<FluentIcon <span class="i-lucide-user size-2.5 text-n-brand" />
icon="person"
type="filled"
size="10"
class="text-woot-500 dark:text-woot-400"
/>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -31,7 +31,7 @@ export const getArticleStatus = status => {
export const HELP_CENTER_MENU_ITEMS = [ export const HELP_CENTER_MENU_ITEMS = [
{ {
label: 'Articles', label: 'Articles',
icon: 'book', icon: 'i-lucide-book',
action: 'portals_articles_index', action: 'portals_articles_index',
value: [ value: [
'portals_articles_index', 'portals_articles_index',
@@ -41,7 +41,7 @@ export const HELP_CENTER_MENU_ITEMS = [
}, },
{ {
label: 'Categories', label: 'Categories',
icon: 'folder', icon: 'i-lucide-folder',
action: 'portals_categories_index', action: 'portals_categories_index',
value: [ value: [
'portals_categories_index', 'portals_categories_index',
@@ -51,13 +51,13 @@ export const HELP_CENTER_MENU_ITEMS = [
}, },
{ {
label: 'Locales', label: 'Locales',
icon: 'translate', icon: 'i-lucide-languages',
action: 'portals_locales_index', action: 'portals_locales_index',
value: ['portals_locales_index'], value: ['portals_locales_index'],
}, },
{ {
label: 'Settings', label: 'Settings',
icon: 'settings', icon: 'i-lucide-settings',
action: 'portals_settings_index', action: 'portals_settings_index',
value: ['portals_settings_index'], value: ['portals_settings_index'],
}, },
@@ -74,25 +74,25 @@ export const ARTICLE_MENU_ITEMS = {
label: 'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.DROPDOWN_MENU.PUBLISH', label: 'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.DROPDOWN_MENU.PUBLISH',
value: ARTICLE_STATUSES.PUBLISHED, value: ARTICLE_STATUSES.PUBLISHED,
action: 'publish', action: 'publish',
icon: 'checkmark', icon: 'i-lucide-check',
}, },
draft: { draft: {
label: 'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.DROPDOWN_MENU.DRAFT', label: 'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.DROPDOWN_MENU.DRAFT',
value: ARTICLE_STATUSES.DRAFT, value: ARTICLE_STATUSES.DRAFT,
action: 'draft', action: 'draft',
icon: 'draft', icon: 'i-lucide-pencil-line',
}, },
archive: { archive: {
label: 'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.DROPDOWN_MENU.ARCHIVE', label: 'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.DROPDOWN_MENU.ARCHIVE',
value: ARTICLE_STATUSES.ARCHIVED, value: ARTICLE_STATUSES.ARCHIVED,
action: 'archive', action: 'archive',
icon: 'archive', icon: 'i-lucide-archive-restore',
}, },
delete: { delete: {
label: 'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.DROPDOWN_MENU.DELETE', label: 'HELP_CENTER.ARTICLES_PAGE.ARTICLE_CARD.CARD.DROPDOWN_MENU.DELETE',
value: 'delete', value: 'delete',
action: 'delete', action: 'delete',
icon: 'delete', icon: 'i-lucide-trash',
}, },
}; };
@@ -135,13 +135,13 @@ export const LOCALE_MENU_ITEMS = [
label: 'HELP_CENTER.LOCALES_PAGE.LOCALE_CARD.DROPDOWN_MENU.MAKE_DEFAULT', label: 'HELP_CENTER.LOCALES_PAGE.LOCALE_CARD.DROPDOWN_MENU.MAKE_DEFAULT',
action: 'change-default', action: 'change-default',
value: 'default', value: 'default',
icon: 'star-emphasis', icon: 'i-lucide-star',
}, },
{ {
label: 'HELP_CENTER.LOCALES_PAGE.LOCALE_CARD.DROPDOWN_MENU.DELETE', label: 'HELP_CENTER.LOCALES_PAGE.LOCALE_CARD.DROPDOWN_MENU.DELETE',
action: 'delete', action: 'delete',
value: 'delete', value: 'delete',
icon: 'delete', icon: 'i-lucide-trash',
}, },
]; ];

View File

@@ -104,7 +104,7 @@
"@iconify-json/ri": "^1.2.1", "@iconify-json/ri": "^1.2.1",
"@histoire/plugin-vue": "0.17.15", "@histoire/plugin-vue": "0.17.15",
"@iconify-json/logos": "^1.2.0", "@iconify-json/logos": "^1.2.0",
"@iconify-json/lucide": "^1.2.5", "@iconify-json/lucide": "^1.2.10",
"@size-limit/file": "^8.2.4", "@size-limit/file": "^8.2.4",
"@vitest/coverage-v8": "2.0.1", "@vitest/coverage-v8": "2.0.1",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
@@ -142,6 +142,13 @@
"pre-push": "sh bin/validate_push" "pre-push": "sh bin/validate_push"
} }
}, },
"pnpm": {
"overrides": {
"vite-node": "2.0.1",
"vite": "5.4.8",
"vitest": "2.0.1"
}
},
"lint-staged": { "lint-staged": {
"app/**/*.{js,vue}": [ "app/**/*.{js,vue}": [
"eslint --fix", "eslint --fix",

51
pnpm-lock.yaml generated
View File

@@ -4,6 +4,11 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
vite-node: 2.0.1
vite: 5.4.8
vitest: 2.0.1
importers: importers:
.: .:
@@ -223,8 +228,8 @@ importers:
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0 version: 1.2.0
'@iconify-json/lucide': '@iconify-json/lucide':
specifier: ^1.2.5 specifier: ^1.2.10
version: 1.2.5 version: 1.2.10
'@iconify-json/ri': '@iconify-json/ri':
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1 version: 1.2.1
@@ -301,7 +306,7 @@ importers:
specifier: ^3.4.13 specifier: ^3.4.13
version: 3.4.13 version: 3.4.13
vite: vite:
specifier: ^5.4.8 specifier: 5.4.8
version: 5.4.8(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) version: 5.4.8(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)
vite-plugin-ruby: vite-plugin-ruby:
specifier: ^5.0.0 specifier: ^5.0.0
@@ -817,7 +822,7 @@ packages:
'@histoire/shared@0.17.17': '@histoire/shared@0.17.17':
resolution: {integrity: sha512-ueGtURysonT0MujCObPCR57+mgZluMEXCrbc2FBgKAD/DoAt38tNwSGsmLldk2O6nTr7lr6ClbVSgWrLwgY6Xw==} resolution: {integrity: sha512-ueGtURysonT0MujCObPCR57+mgZluMEXCrbc2FBgKAD/DoAt38tNwSGsmLldk2O6nTr7lr6ClbVSgWrLwgY6Xw==}
peerDependencies: peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 vite: 5.4.8
'@histoire/vendors@0.17.17': '@histoire/vendors@0.17.17':
resolution: {integrity: sha512-QZvmffdoJlLuYftPIkOU5Q2FPAdG2JjMuQ5jF7NmEl0n1XnmbMqtRkdYTZ4eF6CO1KLZ0Zyf6gBQvoT1uWNcjA==} resolution: {integrity: sha512-QZvmffdoJlLuYftPIkOU5Q2FPAdG2JjMuQ5jF7NmEl0n1XnmbMqtRkdYTZ4eF6CO1KLZ0Zyf6gBQvoT1uWNcjA==}
@@ -838,8 +843,8 @@ packages:
'@iconify-json/logos@1.2.0': '@iconify-json/logos@1.2.0':
resolution: {integrity: sha512-VkU9QSqeZR2guWbecdqkcoZEAJfgJJTUm6QAsypuZQ7Cve6zy39wOXDjp2H31I8QyQy4O3Cz96+pNji6UQFg4w==} resolution: {integrity: sha512-VkU9QSqeZR2guWbecdqkcoZEAJfgJJTUm6QAsypuZQ7Cve6zy39wOXDjp2H31I8QyQy4O3Cz96+pNji6UQFg4w==}
'@iconify-json/lucide@1.2.5': '@iconify-json/lucide@1.2.10':
resolution: {integrity: sha512-ZRw1GRcN5CQ+9BW+yBEFjRNf4pQfsU8gvNVcCY81yVmwKLUegTncGHXjBuspK6HSmsJspOhGBgLqNbHb0dpxfw==} resolution: {integrity: sha512-cR1xpRJ4dnoXlC0ShDjzbrZyu+ICH4OUaYl7S51MhZUO1H040s7asVqv0LsDbofSLDuzWkHCLsBabTTRL0mCUg==}
'@iconify-json/ri@1.2.1': '@iconify-json/ri@1.2.1':
resolution: {integrity: sha512-xI3+xZHBI+wlhQqd6jRRcLD5K8B8vQNyxcSB43myxNZ/SfXIn7Ny28h0jyPo9e0gT8fGhqx6R5PeLz/UBB8jwQ==} resolution: {integrity: sha512-xI3+xZHBI+wlhQqd6jRRcLD5K8B8vQNyxcSB43myxNZ/SfXIn7Ny28h0jyPo9e0gT8fGhqx6R5PeLz/UBB8jwQ==}
@@ -1713,7 +1718,7 @@ packages:
resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies: peerDependencies:
vite: ^5.0.0 vite: 5.4.8
vue: ^3.2.25 vue: ^3.2.25
'@vitest/coverage-v8@2.0.1': '@vitest/coverage-v8@2.0.1':
@@ -2907,7 +2912,7 @@ packages:
resolution: {integrity: sha512-DiRMSIgj340z+zikqf0f3Pj0CTv2/xtdBMBIAO1EARat+QXxMwumbfK41Gi7f9IIBr+UVmomNcwFxVY2EM/vrw==} resolution: {integrity: sha512-DiRMSIgj340z+zikqf0f3Pj0CTv2/xtdBMBIAO1EARat+QXxMwumbfK41Gi7f9IIBr+UVmomNcwFxVY2EM/vrw==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 vite: 5.4.8
hotkeys-js@3.8.7: hotkeys-js@3.8.7:
resolution: {integrity: sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg==} resolution: {integrity: sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg==}
@@ -4632,11 +4637,6 @@ packages:
videojs-wavesurfer@3.8.0: videojs-wavesurfer@3.8.0:
resolution: {integrity: sha512-qHucCBiEW+4dZ0Zp1k4R1elprUOV+QDw87UDA9QRXtO7GK/MrSdoe/TMFxP9SLnJCiX9xnYdf4OQgrmvJ9UVVw==} resolution: {integrity: sha512-qHucCBiEW+4dZ0Zp1k4R1elprUOV+QDw87UDA9QRXtO7GK/MrSdoe/TMFxP9SLnJCiX9xnYdf4OQgrmvJ9UVVw==}
vite-node@0.34.7:
resolution: {integrity: sha512-0Yzb96QzHmqIKIs/x2q/sqG750V/EF6yDkS2p1WjJc1W2bgRSuQjf5vB9HY8h2nVb5j4pO5paS5Npcv3s69YUg==}
engines: {node: '>=v14.18.0'}
hasBin: true
vite-node@2.0.1: vite-node@2.0.1:
resolution: {integrity: sha512-nVd6kyhPAql0s+xIVJzuF+RSRH8ZimNrm6U8ZvTA4MXv8CHI17TFaQwRaFiK75YX6XeFqZD4IoAaAfi9OR1XvQ==} resolution: {integrity: sha512-nVd6kyhPAql0s+xIVJzuF+RSRH8ZimNrm6U8ZvTA4MXv8CHI17TFaQwRaFiK75YX6XeFqZD4IoAaAfi9OR1XvQ==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
@@ -4645,7 +4645,7 @@ packages:
vite-plugin-ruby@5.0.0: vite-plugin-ruby@5.0.0:
resolution: {integrity: sha512-c8PjTp21Ah/ttgnNUyu0qvCXZI08Jr9I24oUKg3TRIRhF5GcOZ++6wtlTCrNFd9COEQbpXHxlRIXd/MEg0iZJw==} resolution: {integrity: sha512-c8PjTp21Ah/ttgnNUyu0qvCXZI08Jr9I24oUKg3TRIRhF5GcOZ++6wtlTCrNFd9COEQbpXHxlRIXd/MEg0iZJw==}
peerDependencies: peerDependencies:
vite: '>=5.0.0' vite: 5.4.8
vite@5.4.8: vite@5.4.8:
resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==}
@@ -5496,7 +5496,7 @@ snapshots:
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
'@iconify-json/lucide@1.2.5': '@iconify-json/lucide@1.2.10':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
@@ -7977,7 +7977,7 @@ snapshots:
shiki-es: 0.2.0 shiki-es: 0.2.0
sirv: 2.0.4 sirv: 2.0.4
vite: 5.4.8(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) vite: 5.4.8(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)
vite-node: 0.34.7(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) vite-node: 2.0.1(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- bufferutil - bufferutil
@@ -9894,25 +9894,6 @@ snapshots:
video.js: 7.18.1 video.js: 7.18.1
wavesurfer.js: 7.8.6 wavesurfer.js: 7.8.6
vite-node@0.34.7(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0):
dependencies:
cac: 6.7.14
debug: 4.3.7
mlly: 1.7.1
pathe: 1.1.2
picocolors: 1.1.0
vite: 5.4.8(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)
transitivePeerDependencies:
- '@types/node'
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
vite-node@2.0.1(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0): vite-node@2.0.1(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0):
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14

View File

@@ -28,6 +28,7 @@ const tailwindConfig = {
'./app/javascript/portal/**/*.vue', './app/javascript/portal/**/*.vue',
'./app/javascript/shared/**/*.vue', './app/javascript/shared/**/*.vue',
'./app/javascript/survey/**/*.vue', './app/javascript/survey/**/*.vue',
'./app/javascript/dashboard/helper/**/*.js',
'./app/views/**/*.html.erb', './app/views/**/*.html.erb',
], ],
theme: { theme: {