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>
import { computed, ref } from 'vue';
import { OnClickOutside } from '@vueuse/components';
import { useI18n } from 'vue-i18n';
import { dynamicTime } from 'shared/helpers/timeHelper';
import {
@@ -9,11 +8,11 @@ import {
ARTICLE_STATUSES,
} from 'dashboard/helper/portalHelper';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import CardLayout from 'dashboard/components-next/CardLayout.vue';
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import Thumbnail from 'dashboard/components-next/thumbnail/Thumbnail.vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
const props = defineProps({
id: {
@@ -72,11 +71,11 @@ const articleMenuItems = computed(() => {
const statusTextColor = computed(() => {
switch (props.status) {
case 'archived':
return '!text-n-slate-12';
return 'text-n-slate-12';
case 'draft':
return '!text-n-amber-11';
return 'text-n-amber-11';
default:
return '!text-n-teal-11';
return 'text-n-teal-11';
}
});
@@ -127,19 +126,28 @@ const handleClick = id => {
<template #header>
<div class="flex justify-between gap-1">
<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)"
>
{{ title }}
</span>
<div class="relative group" @click.stop>
<OnClickOutside @trigger="isOpen = false">
<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="statusTextColor"
>
{{ statusText }}
</span>
<div
v-on-clickaway="() => (isOpen = false)"
class="relative flex items-center group"
@click.stop
>
<Button
variant="ghost"
size="sm"
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"
:label="statusText"
:class="statusTextColor"
icon="i-lucide-ellipsis-vertical"
color="slate"
size="xs"
class="group-hover:bg-n-solid-2 !p-0.5 rounded-md"
@click="isOpen = !isOpen"
/>
<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"
@action="handleArticleAction($event)"
/>
</OnClickOutside>
</div>
</div>
</div>
</template>
@@ -171,7 +179,7 @@ const handleClick = id => {
<div
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">
{{
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 items-center justify-start gap-2">
<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)"
>
{{ categoryTitleWithIcon }}
@@ -102,10 +102,10 @@ const handleAction = ({ action, value }) => {
<div class="relative group" @click.stop>
<OnClickOutside @trigger="isOpen = false">
<Button
variant="ghost"
size="sm"
icon="more-vertical"
class="w-8 z-60 group-hover:bg-slate-100 dark:group-hover:bg-slate-800"
icon="i-lucide-ellipsis-vertical"
color="slate"
size="xs"
class="rounded-md group-hover:bg-n-solid-2"
@click="isOpen = !isOpen"
/>
<DropdownMenu

View File

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

View File

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

View File

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

View File

@@ -52,7 +52,7 @@ const handleAction = ({ action, value }) => {
</script>
<template>
<CardLayout class="ltr:pr-2 rtl:pl-2">
<CardLayout>
<template #header>
<div class="flex justify-between gap-2">
<div class="flex items-center justify-start gap-2">
@@ -63,12 +63,12 @@ const handleAction = ({ action, value }) => {
</span>
<span
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') }}
</span>
</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">
<span
class="text-sm text-slate-500 dark:text-slate-400 whitespace-nowrap"
@@ -95,10 +95,10 @@ const handleAction = ({ action, value }) => {
<div class="relative group">
<OnClickOutside @trigger="showDropdownMenu = false">
<Button
variant="ghost"
size="sm"
icon="more-vertical"
class="w-8 group-hover:bg-slate-100 dark:group-hover:bg-slate-800"
icon="i-lucide-ellipsis-vertical"
color="slate"
size="xs"
class="rounded-md group-hover:bg-n-solid-2"
@click="showDropdownMenu = !showDropdownMenu"
/>

View File

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

View File

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

View File

@@ -63,10 +63,10 @@ onMounted(() => {
}}
</h3>
<Button
icon="dismiss"
icon="i-lucide-x"
size="sm"
variant="ghost"
class="w-8 hover:text-n-slate-11"
class="hover:text-n-slate-11"
@click="emit('close')"
/>
</div>
@@ -89,7 +89,7 @@ onMounted(() => {
'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-class="max-h-[150px]"
auto-height

View File

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

View File

@@ -198,10 +198,10 @@ defineExpose({ state, isSubmitDisabled });
<OnClickOutside @trigger="isEmojiPickerOpen = false">
<Button
:label="state.icon"
variant="secondary"
color="slate"
size="sm"
:icon="!state.icon ? 'emoji-add' : ''"
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"
:icon="!state.icon ? 'i-lucide-smile-plus' : ''"
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"
/>
<EmojiInput
@@ -243,10 +243,10 @@ defineExpose({ state, isSubmitDisabled });
class="flex items-center justify-between w-full gap-3"
>
<Button
variant="ghost"
variant="faded"
color="slate"
:label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_DIALOG.BUTTONS.CANCEL')"
text-variant="default"
class="w-full bg-n-alpha-2 hover:bg-n-alpha-3"
class="w-full bg-n-alpha-2 n-blue-text hover:bg-n-alpha-3"
@click="handleCancel"
/>
<Button

View File

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

View File

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

View File

@@ -196,16 +196,17 @@ const handleAvatarDelete = () => {
/>
</div>
<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
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') }}
</label>
<Input
v-model="state.name"
:placeholder="t('HELP_CENTER.PORTAL_SETTINGS.FORM.NAME.PLACEHOLDER')"
class="w-[432px]"
:message-type="nameError ? 'error' : 'info'"
:message="nameError"
custom-input-class="!bg-transparent dark:!bg-transparent"
@@ -213,9 +214,11 @@ const handleAvatarDelete = () => {
@blur="v$.name.$touch()"
/>
</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
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') }}
</label>
@@ -224,13 +227,14 @@ const handleAvatarDelete = () => {
:placeholder="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.HEADER_TEXT.PLACEHOLDER')
"
class="w-[432px]"
custom-input-class="!bg-transparent dark:!bg-transparent"
/>
</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
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') }}
</label>
@@ -239,13 +243,14 @@ const handleAvatarDelete = () => {
:placeholder="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.PAGE_TITLE.PLACEHOLDER')
"
class="w-[432px]"
custom-input-class="!bg-transparent dark:!bg-transparent"
/>
</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
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') }}
</label>
@@ -254,7 +259,6 @@ const handleAvatarDelete = () => {
:placeholder="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.HOME_PAGE_LINK.PLACEHOLDER')
"
class="w-[432px]"
:message-type="homePageLinkError ? 'error' : 'info'"
:message="homePageLinkError"
custom-input-class="!bg-transparent dark:!bg-transparent"
@@ -262,16 +266,17 @@ const handleAvatarDelete = () => {
@blur="v$.homePageLink.$touch()"
/>
</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
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') }}
</label>
<Input
v-model="state.slug"
:placeholder="t('HELP_CENTER.PORTAL_SETTINGS.FORM.SLUG.PLACEHOLDER')"
class="w-[432px]"
:message-type="slugError ? 'error' : 'info'"
:message="slugError || buildPortalURL(state.slug)"
custom-input-class="!bg-transparent dark:!bg-transparent"
@@ -279,9 +284,11 @@ const handleAvatarDelete = () => {
@blur="v$.slug.$touch()"
/>
</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
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') }}
</label>
@@ -294,12 +301,12 @@ const handleAvatarDelete = () => {
:message="
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 class="flex items-start justify-between w-full gap-2">
<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') }}
</label>

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<script setup>
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({
src: {
@@ -57,7 +57,7 @@ const handleDismiss = event => {
<template>
<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 }"
>
<img
@@ -71,29 +71,27 @@ const handleDismiss = event => {
v-else
class="flex items-center justify-center w-full h-full rounded-xl bg-n-alpha-2"
>
<FluentIcon
icon="building-lucide"
icon-lib="lucide"
:size="iconSize"
class="dark:text-n-brand/50 text-n-brand/30"
<Icon
icon="i-lucide-building-2"
class="text-n-brand/50"
:style="{ width: `${iconSize}`, height: `${iconSize}` }"
/>
</div>
<div
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"
>
<FluentIcon icon="dismiss" :size="16" class="text-n-slate-11" />
<Icon icon="i-lucide-x" class="text-n-slate-11 size-4" />
</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"
@click="handleUploadAvatar"
>
<FluentIcon
icon="upload-lucide"
icon-lib="lucide"
:size="iconSize"
class="text-white dark:text-white"
<Icon
icon="i-lucide-upload"
class="text-white"
:style="{ width: `${iconSize}`, height: `${iconSize}` }"
/>
<input
ref="fileInput"

View File

@@ -2,8 +2,7 @@
import { defineProps } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from 'dashboard/components-next/button/Button.vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
defineProps({
items: {
@@ -33,21 +32,17 @@ const onClick = event => {
<nav :aria-label="t('BREADCRUMB.ARIA_LABEL')" class="flex items-center h-8">
<ol class="flex items-center mb-0">
<li v-for="(item, index) in items" :key="index" class="flex items-center">
<Button
<button
v-if="index === 0"
:label="item.label"
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"
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"
@click="onClick"
/>
>
<span class="min-w-0 truncate">{{ item.label }}</span>
</button>
<template v-else>
<FluentIcon
icon="chevron-lucide-right"
size="18"
icon-lib="lucide"
class="flex-shrink-0 mx-2 text-slate-300 dark:text-slate-500"
<Icon
icon="i-lucide-chevron-right"
class="flex-shrink-0 mx-2 size-4 text-n-slate-11 dark:text-n-slate-11"
/>
<span
class="text-sm truncate text-slate-900 dark:text-slate-50 max-w-56"

View File

@@ -1,125 +1,123 @@
<script setup>
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>
<template>
<Story title="Components/Button" :layout="{ type: 'grid', width: '400' }">
<Variant title="Default">
<div class="p-4 bg-white dark:bg-slate-900">
<Button label="Default Button" />
<Story title="Components/Button" :layout="{ type: 'grid', width: '800px' }">
<!-- Basic Variants -->
<Variant title="Basic Variants">
<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>
</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">
<Button label="Disabled" disabled />
<Button label="Disabled" variant="outline" disabled />
<Button label="Disabled" disabled icon="delete" variant="outline" />
<Button label="Disabled Outline" variant="outline" disabled />
<Button label="Disabled Icon" icon="delete" disabled />
<Button
label="Disabled"
label="Disabled Destructive"
color="ruby"
disabled
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"
/>
</div>
</Variant>
<Variant title="Disabled with icon">
<div class="p-4 bg-white dark:bg-slate-900">
<Button label="Disabled Button" icon="emoji-add" disabled />
<!-- Color Combinations -->
<Variant title="Color & Variant Combinations">
<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>
</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">
<Button label="Default" variant="default" />
<Button label="Destructive" variant="destructive" />
<Button label="Outline" variant="outline" />
<Button label="Secondary" variant="secondary" />
<Button label="Ghost" variant="ghost" />
<Button label="Link" variant="link" />
<template v-for="size in SIZES" :key="size">
<Button
:label="`${size} Leading`"
icon="i-lucide-plus"
:size="size"
/>
<Button
:label="`${size} Trailing`"
icon="i-lucide-plus"
trailing-icon
:size="size"
/>
<Button icon="i-lucide-plus" :size="size" />
</template>
</div>
</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">
<Button icon="emoji-add" variant="default" />
<Button icon="emoji-add" variant="destructive" />
<Button icon="emoji-add" variant="outline" />
<Button icon="emoji-add" variant="secondary" />
<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" />
<Button label="Ghost Button" variant="ghost" />
<Button label="Ghost with Icon" variant="ghost" icon="i-lucide-plus" />
<Button label="Link Button" variant="link" />
<Button label="Link with Icon" variant="link" icon="i-lucide-plus" />
</div>
</Variant>
</Story>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<script setup>
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';
defineProps({
@@ -29,29 +29,28 @@ const handleAction = (action, value) => {
<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"
>
<Button
<button
v-for="item in menuItems"
:key="item.action"
:label="item.label"
:icon="item.icon"
:emoji="item.emoji"
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"
:class="{
'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"
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)"
>
<template #leftPrefix>
<Thumbnail
v-if="item.thumbnail"
:author="item.thumbnail"
:name="item.thumbnail.name"
:size="thumbnailSize"
:src="item.thumbnail.src"
/>
</template>
</Button>
<Thumbnail
v-if="item.thumbnail"
:author="item.thumbnail"
:name="item.thumbnail.name"
:size="thumbnailSize"
:src="item.thumbnail.src"
/>
<Icon v-if="item.icon" :icon="item.icon" />
<span v-if="item.emoji">{{ item.emoji }}</span>
<span v-if="item.label" class="text-sm">{{ item.label }}</span>
</button>
</div>
</template>

View File

@@ -69,7 +69,7 @@ const handleInput = event => {
</script>
<template>
<div class="relative flex flex-col gap-1">
<div class="relative flex flex-col min-w-0 gap-1">
<label
v-if="label"
:for="id"
@@ -92,7 +92,7 @@ const handleInput = event => {
/>
<p
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"
>
{{ message }}

View File

@@ -65,18 +65,18 @@ const pageInfo = computed(() => {
</div>
<div class="flex items-center gap-2">
<Button
icon="chevrons-lucide-left"
icon-lib="lucide"
icon="i-lucide-chevrons-left"
variant="ghost"
size="sm"
class="!w-8 !h-6"
:disabled="isFirstPage"
@click="changePage(1)"
/>
<Button
icon="chevron-lucide-left"
icon-lib="lucide"
icon="i-lucide-chevron-left"
variant="ghost"
size="sm"
class="!w-8 !h-6"
:disabled="isFirstPage"
@click="changePage(currentPage - 1)"
/>
@@ -87,18 +87,18 @@ const pageInfo = computed(() => {
<span>{{ pageInfo }}</span>
</div>
<Button
icon="chevron-lucide-right"
icon-lib="lucide"
icon="i-lucide-chevron-right"
variant="ghost"
size="sm"
class="!w-8 !h-6"
:disabled="isLastPage"
@click="changePage(currentPage + 1)"
/>
<Button
icon="chevrons-lucide-right"
icon-lib="lucide"
icon="i-lucide-chevrons-right"
variant="ghost"
size="sm"
class="!w-8 !h-6"
:disabled="isLastPage"
@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="[
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',
]"
@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="
showDivider(index)
? 'bg-slate-75 dark:bg-slate-800'
? 'bg-n-strong'
: 'bg-transparent dark:bg-transparent'
"
/>

View File

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

View File

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

View File

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

View File

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

51
pnpm-lock.yaml generated
View File

@@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
vite-node: 2.0.1
vite: 5.4.8
vitest: 2.0.1
importers:
.:
@@ -223,8 +228,8 @@ importers:
specifier: ^1.2.0
version: 1.2.0
'@iconify-json/lucide':
specifier: ^1.2.5
version: 1.2.5
specifier: ^1.2.10
version: 1.2.10
'@iconify-json/ri':
specifier: ^1.2.1
version: 1.2.1
@@ -301,7 +306,7 @@ importers:
specifier: ^3.4.13
version: 3.4.13
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)
vite-plugin-ruby:
specifier: ^5.0.0
@@ -817,7 +822,7 @@ packages:
'@histoire/shared@0.17.17':
resolution: {integrity: sha512-ueGtURysonT0MujCObPCR57+mgZluMEXCrbc2FBgKAD/DoAt38tNwSGsmLldk2O6nTr7lr6ClbVSgWrLwgY6Xw==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
vite: 5.4.8
'@histoire/vendors@0.17.17':
resolution: {integrity: sha512-QZvmffdoJlLuYftPIkOU5Q2FPAdG2JjMuQ5jF7NmEl0n1XnmbMqtRkdYTZ4eF6CO1KLZ0Zyf6gBQvoT1uWNcjA==}
@@ -838,8 +843,8 @@ packages:
'@iconify-json/logos@1.2.0':
resolution: {integrity: sha512-VkU9QSqeZR2guWbecdqkcoZEAJfgJJTUm6QAsypuZQ7Cve6zy39wOXDjp2H31I8QyQy4O3Cz96+pNji6UQFg4w==}
'@iconify-json/lucide@1.2.5':
resolution: {integrity: sha512-ZRw1GRcN5CQ+9BW+yBEFjRNf4pQfsU8gvNVcCY81yVmwKLUegTncGHXjBuspK6HSmsJspOhGBgLqNbHb0dpxfw==}
'@iconify-json/lucide@1.2.10':
resolution: {integrity: sha512-cR1xpRJ4dnoXlC0ShDjzbrZyu+ICH4OUaYl7S51MhZUO1H040s7asVqv0LsDbofSLDuzWkHCLsBabTTRL0mCUg==}
'@iconify-json/ri@1.2.1':
resolution: {integrity: sha512-xI3+xZHBI+wlhQqd6jRRcLD5K8B8vQNyxcSB43myxNZ/SfXIn7Ny28h0jyPo9e0gT8fGhqx6R5PeLz/UBB8jwQ==}
@@ -1713,7 +1718,7 @@ packages:
resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0
vite: 5.4.8
vue: ^3.2.25
'@vitest/coverage-v8@2.0.1':
@@ -2907,7 +2912,7 @@ packages:
resolution: {integrity: sha512-DiRMSIgj340z+zikqf0f3Pj0CTv2/xtdBMBIAO1EARat+QXxMwumbfK41Gi7f9IIBr+UVmomNcwFxVY2EM/vrw==}
hasBin: true
peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
vite: 5.4.8
hotkeys-js@3.8.7:
resolution: {integrity: sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg==}
@@ -4632,11 +4637,6 @@ packages:
videojs-wavesurfer@3.8.0:
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:
resolution: {integrity: sha512-nVd6kyhPAql0s+xIVJzuF+RSRH8ZimNrm6U8ZvTA4MXv8CHI17TFaQwRaFiK75YX6XeFqZD4IoAaAfi9OR1XvQ==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -4645,7 +4645,7 @@ packages:
vite-plugin-ruby@5.0.0:
resolution: {integrity: sha512-c8PjTp21Ah/ttgnNUyu0qvCXZI08Jr9I24oUKg3TRIRhF5GcOZ++6wtlTCrNFd9COEQbpXHxlRIXd/MEg0iZJw==}
peerDependencies:
vite: '>=5.0.0'
vite: 5.4.8
vite@5.4.8:
resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==}
@@ -5496,7 +5496,7 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/lucide@1.2.5':
'@iconify-json/lucide@1.2.10':
dependencies:
'@iconify/types': 2.0.0
@@ -7977,7 +7977,7 @@ snapshots:
shiki-es: 0.2.0
sirv: 2.0.4
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:
- '@types/node'
- bufferutil
@@ -9894,25 +9894,6 @@ snapshots:
video.js: 7.18.1
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):
dependencies:
cac: 6.7.14

View File

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