chore: Update theme colors and add new Inter variable fonts (#13347)
# Pull Request Template ## Description This PR includes the following updates: 1. Updated the design system color tokens by introducing new tokens for surfaces, overlays, buttons, labels, and cards, along with refinements to existing shades. 2. Refreshed both light and dark themes with adjusted background, border, and solid colors. 3. Replaced static Inter font files with the Inter variable font (including italic), supporting weights from 100–900. 4. Added custom font weights (420, 440, 460, 520) along with custom typography classes to enable more fine-grained and consistent typography control. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
@@ -131,7 +131,7 @@ export default {
|
||||
<div
|
||||
v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem"
|
||||
id="app"
|
||||
class="flex flex-col w-full h-screen min-h-0"
|
||||
class="flex flex-col w-full h-screen min-h-0 bg-n-background"
|
||||
:dir="isRTL ? 'rtl' : 'ltr'"
|
||||
>
|
||||
<UpdateBanner :latest-chatwoot-version="latestChatwootVersion" />
|
||||
|
||||
@@ -107,21 +107,39 @@
|
||||
--violet-11: 101 85 183;
|
||||
--violet-12: 47 38 95;
|
||||
|
||||
--background-color: 253 253 253;
|
||||
--text-blue: 8 109 224;
|
||||
--background-color: 247 247 247;
|
||||
--surface-1: 254 254 254;
|
||||
--surface-2: 255 255 255;
|
||||
--surface-active: 255 255 255;
|
||||
--background-input-box: 0, 0, 0, 0.03;
|
||||
--text-blue: 1 22 44;
|
||||
--text-purple: 2 4 49;
|
||||
--text-amber: 37 24 1;
|
||||
--border-container: 236 236 236;
|
||||
--border-strong: 235 235 235;
|
||||
--border-strong: 226 227 231;
|
||||
--border-weak: 234 234 234;
|
||||
--border-blue-strong: 18 61 117;
|
||||
--solid-1: 255 255 255;
|
||||
--solid-2: 255 255 255;
|
||||
--solid-3: 255 255 255;
|
||||
--solid-active: 255 255 255;
|
||||
--solid-amber: 252 232 193;
|
||||
--solid-amber: 255 228 181;
|
||||
--solid-blue: 218 236 255;
|
||||
--solid-blue-2: 251 253 255;
|
||||
--solid-iris: 230 231 255;
|
||||
--solid-purple: 230 231 255;
|
||||
--solid-red: 254 200 201;
|
||||
--solid-amber-button: 255 221 141;
|
||||
--card-color: 255 255 255;
|
||||
--overlay: 0, 0, 0, 0.12;
|
||||
--overlay-avatar: 255, 255, 255, 0.67;
|
||||
--button-color: 255 255 255;
|
||||
--button-hover-color: 255, 255, 255, 0.2;
|
||||
--label-background: 247 247 247;
|
||||
--label-border: 0, 0, 0, 0.04;
|
||||
|
||||
--alpha-1: 67, 67, 67, 0.06;
|
||||
--alpha-2: 201, 202, 207, 0.15;
|
||||
--alpha-1: 215, 215, 215, 0.22;
|
||||
--alpha-2: 196, 197, 198, 0.22;
|
||||
--alpha-3: 255, 255, 255, 0.96;
|
||||
--black-alpha-1: 0, 0, 0, 0.12;
|
||||
--black-alpha-2: 0, 0, 0, 0.04;
|
||||
@@ -235,25 +253,43 @@
|
||||
--violet-11: 169 153 236;
|
||||
--violet-12: 226 221 254;
|
||||
|
||||
--background-color: 18 18 19;
|
||||
--border-strong: 52 52 52;
|
||||
--border-weak: 38 38 42;
|
||||
--background-color: 28 29 32;
|
||||
--surface-1: 20 21 23;
|
||||
--surface-2: 22 23 26;
|
||||
--surface-active: 53 57 66;
|
||||
--background-input-box: 255, 255, 255, 0.02;
|
||||
--text-blue: 213 234 255;
|
||||
--text-purple: 232 233 254;
|
||||
--text-amber: 255 247 234;
|
||||
--border-strong: 46 45 50;
|
||||
--border-weak: 31 31 37;
|
||||
--border-blue-strong: 201 226 255;
|
||||
--solid-1: 23 23 26;
|
||||
--solid-2: 29 30 36;
|
||||
--solid-3: 44 45 54;
|
||||
--solid-active: 53 57 66;
|
||||
--solid-amber: 42 37 30;
|
||||
--solid-blue: 16 49 91;
|
||||
--solid-amber: 56 50 41;
|
||||
--solid-blue: 15 57 102;
|
||||
--solid-blue-2: 26 29 35;
|
||||
--solid-iris: 38 42 101;
|
||||
--text-blue: 126 182 255;
|
||||
--solid-purple: 51 51 107;
|
||||
--solid-red: 90 33 34;
|
||||
--solid-amber-button: 255 221 141;
|
||||
--card-color: 28 30 34;
|
||||
--overlay: 0, 0, 0, 0.4;
|
||||
--overlay-avatar: 0, 0, 0, 0.05;
|
||||
--button-color: 42 43 51;
|
||||
--button-hover-color: 0, 0, 0, 0.15;
|
||||
--label-background: 36 38 45;
|
||||
--label-border: 255, 255, 255, 0.03;
|
||||
|
||||
--alpha-1: 36, 36, 36, 0.8;
|
||||
--alpha-2: 139, 147, 182, 0.15;
|
||||
--alpha-3: 36, 38, 45, 0.9;
|
||||
--alpha-1: 35, 36, 42, 0.8;
|
||||
--alpha-2: 147, 153, 176, 0.12;
|
||||
--alpha-3: 33, 34, 38, 0.95;
|
||||
--black-alpha-1: 0, 0, 0, 0.3;
|
||||
--black-alpha-2: 0, 0, 0, 0.2;
|
||||
--border-blue: 39, 129, 246, 0.5;
|
||||
--border-container: 236, 236, 236, 0;
|
||||
--border-container: 255, 255, 255, 0;
|
||||
--white-alpha: 255, 255, 255, 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@apply bg-n-slate-3;
|
||||
|
||||
&::after {
|
||||
@apply text-n-blue-text;
|
||||
@apply text-n-blue-11;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ onMounted(() => {
|
||||
)
|
||||
"
|
||||
:items="filteredTags"
|
||||
class="[&>button]:!text-n-blue-text [&>div]:min-w-64"
|
||||
class="[&>button]:!text-n-blue-11 [&>div]:min-w-64"
|
||||
@add="onClickAddTag"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ const handleButtonClick = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-background">
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-surface-1">
|
||||
<header class="sticky top-0 z-10 px-6 lg:px-0">
|
||||
<div class="w-full max-w-[60rem] mx-auto">
|
||||
<div class="flex items-center justify-between w-full h-20 gap-2">
|
||||
|
||||
@@ -306,7 +306,7 @@ defineExpose({ prepareCampaignDetails, isSubmitDisabled });
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAMPAIGN.LIVE_CHAT.CREATE.FORM.BUTTONS.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -174,7 +174,7 @@ const handleSubmit = async () => {
|
||||
color="slate"
|
||||
type="button"
|
||||
:label="t('CAMPAIGN.SMS.CREATE.FORM.BUTTONS.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -251,7 +251,7 @@ watch(
|
||||
color="slate"
|
||||
type="button"
|
||||
:label="t('CAMPAIGN.WHATSAPP.CREATE.FORM.BUTTONS.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -21,7 +21,7 @@ const updateCurrentPage = page => {
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="flex w-full h-full gap-4 overflow-hidden justify-evenly bg-n-background"
|
||||
class="flex w-full h-full gap-4 overflow-hidden justify-evenly bg-n-surface-1"
|
||||
>
|
||||
<div class="flex flex-col w-full h-full transition-all duration-300">
|
||||
<CompanyHeader
|
||||
|
||||
@@ -73,7 +73,7 @@ const closeMobileSidebar = () => {
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="flex w-full h-full overflow-hidden justify-evenly bg-n-background"
|
||||
class="flex w-full h-full overflow-hidden justify-evenly bg-n-surface-1"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col w-full h-full transition-all duration-300 ltr:2xl:ml-56 rtl:2xl:mr-56"
|
||||
|
||||
@@ -73,7 +73,7 @@ defineExpose({ dialogRef });
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
download="import-contacts-sample.csv"
|
||||
class="text-n-blue-text"
|
||||
class="text-n-blue-11"
|
||||
>
|
||||
{{
|
||||
t('CONTACTS_LAYOUT.HEADER.ACTIONS.IMPORT_CONTACT.DOWNLOAD_LABEL')
|
||||
|
||||
@@ -78,7 +78,7 @@ const showPagination = computed(() => {
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="flex w-full h-full gap-4 overflow-hidden justify-evenly bg-n-background"
|
||||
class="flex w-full h-full gap-4 overflow-hidden justify-evenly bg-n-surface-1"
|
||||
>
|
||||
<div class="flex flex-col w-full h-full transition-all duration-300">
|
||||
<ContactListHeaderWrapper
|
||||
|
||||
@@ -130,7 +130,7 @@ const onMergeContacts = async () => {
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CONTACTS_LAYOUT.SIDEBAR.MERGE.BUTTONS.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="resetState"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -128,7 +128,7 @@ const handleInputUpdate = async () => {
|
||||
'cursor-pointer text-n-slate-11 hover:text-n-slate-12 py-2 select-none font-medium':
|
||||
!isEditingView,
|
||||
'text-n-slate-12 truncate': isEditingView && !isAttributeTypeLink,
|
||||
'truncate hover:text-n-brand text-n-blue-text':
|
||||
'truncate hover:text-n-brand text-n-blue-11':
|
||||
isEditingView && isAttributeTypeLink,
|
||||
}"
|
||||
@click="toggleEditValue(!isEditingView)"
|
||||
|
||||
@@ -37,7 +37,7 @@ defineProps({
|
||||
<div
|
||||
class="flex flex-col items-center justify-end w-full h-full pb-20"
|
||||
:class="{
|
||||
'absolute inset-x-0 bottom-0 bg-gradient-to-t from-n-background from-25% dark:from-n-background to-transparent':
|
||||
'absolute inset-x-0 bottom-0 bg-gradient-to-t from-n-surface-1 from-25% to-transparent':
|
||||
showBackdrop,
|
||||
}"
|
||||
>
|
||||
@@ -48,14 +48,12 @@ defineProps({
|
||||
}"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center gap-3">
|
||||
<h2
|
||||
class="text-3xl font-medium text-center text-n-slate-12 font-interDisplay"
|
||||
>
|
||||
<h2 class="text-3xl font-medium text-center text-n-slate-12">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<p
|
||||
v-if="subtitle"
|
||||
class="max-w-xl text-base text-center text-n-slate-11 font-interDisplay tracking-[0.3px]"
|
||||
class="max-w-xl text-base text-center text-n-slate-11 tracking-[0.3px]"
|
||||
>
|
||||
{{ subtitle }}
|
||||
</p>
|
||||
|
||||
@@ -126,7 +126,7 @@ const handleClick = id => {
|
||||
<CardLayout>
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<span
|
||||
class="text-base cursor-pointer hover:underline underline-offset-2 hover:text-n-blue-text text-n-slate-12 line-clamp-1"
|
||||
class="text-base cursor-pointer hover:underline underline-offset-2 hover:text-n-blue-11 text-n-slate-12 line-clamp-1"
|
||||
@click="handleClick(id)"
|
||||
>
|
||||
{{ title }}
|
||||
|
||||
@@ -83,7 +83,7 @@ const handleAction = ({ action, value }) => {
|
||||
<div class="flex justify-between w-full gap-2">
|
||||
<div class="flex items-center justify-start w-full min-w-0 gap-2">
|
||||
<span
|
||||
class="text-base truncate cursor-pointer hover:underline underline-offset-2 hover:text-n-blue-text text-n-slate-12"
|
||||
class="text-base truncate cursor-pointer hover:underline underline-offset-2 hover:text-n-blue-11 text-n-slate-12"
|
||||
@click="handleClick(slug)"
|
||||
>
|
||||
{{ categoryTitleWithIcon }}
|
||||
|
||||
@@ -58,7 +58,7 @@ const togglePortalSwitcher = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-background">
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-surface-1">
|
||||
<header class="sticky top-0 z-10 px-6 pb-3 lg:px-0">
|
||||
<div class="w-full max-w-[60rem] mx-auto lg:px-6">
|
||||
<div
|
||||
|
||||
@@ -60,7 +60,7 @@ const handleAction = ({ action, value }) => {
|
||||
</span>
|
||||
<span
|
||||
v-if="isDefault"
|
||||
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"
|
||||
class="bg-n-alpha-2 h-6 inline-flex items-center justify-center rounded-md text-xs border-px border-transparent text-n-blue-11 px-2 py-0.5"
|
||||
>
|
||||
{{ $t('HELP_CENTER.LOCALES_PAGE.LOCALE_CARD.DEFAULT') }}
|
||||
</span>
|
||||
|
||||
@@ -246,7 +246,7 @@ defineExpose({ state, isSubmitDisabled });
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('HELP_CENTER.CATEGORY_PAGE.CATEGORY_DIALOG.BUTTONS.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -100,7 +100,7 @@ const formattedMessage = computed(() => {
|
||||
|
||||
const notificationDetails = computed(() => {
|
||||
const type = props.inboxItem?.notificationType?.toUpperCase() || '';
|
||||
const [icon = '', color = 'text-n-blue-text'] =
|
||||
const [icon = '', color = 'text-n-blue-11'] =
|
||||
NOTIFICATION_TYPES_MAPPING[type] || [];
|
||||
return { text: type ? t(`INBOX.TYPES_NEXT.${type}`) : '', icon, color };
|
||||
});
|
||||
@@ -181,11 +181,11 @@ onBeforeMount(contextMenuActions.close);
|
||||
: 'i-lucide-alarm-clock-off'
|
||||
"
|
||||
class="flex-shrink-0 size-4"
|
||||
:class="!isUnread ? 'text-n-slate-11' : 'text-n-blue-text'"
|
||||
:class="!isUnread ? 'text-n-slate-11' : 'text-n-blue-11'"
|
||||
/>
|
||||
<span
|
||||
class="text-xs font-medium truncate"
|
||||
:class="!isUnread ? 'text-n-slate-11' : 'text-n-blue-text'"
|
||||
:class="!isUnread ? 'text-n-slate-11' : 'text-n-blue-11'"
|
||||
>
|
||||
{{ snoozedText }}
|
||||
</span>
|
||||
|
||||
@@ -103,11 +103,11 @@ const STYLE_CONFIG = {
|
||||
solid:
|
||||
'bg-n-brand text-white hover:enabled:brightness-110 focus-visible:brightness-110 outline-transparent',
|
||||
faded:
|
||||
'bg-n-brand/10 text-n-blue-text hover:enabled:bg-n-brand/20 focus-visible:bg-n-brand/20 outline-transparent',
|
||||
outline: 'text-n-blue-text outline-n-brand',
|
||||
'bg-n-brand/10 text-n-blue-11 hover:enabled:bg-n-brand/20 focus-visible:bg-n-brand/20 outline-transparent',
|
||||
outline: 'text-n-blue-11 outline-n-brand',
|
||||
ghost:
|
||||
'text-n-blue-text hover:enabled:bg-n-alpha-2 focus-visible:bg-n-alpha-2 outline-transparent',
|
||||
link: 'text-n-blue-text hover:enabled:underline focus-visible:underline outline-transparent',
|
||||
'text-n-blue-11 hover:enabled:bg-n-alpha-2 focus-visible:bg-n-alpha-2 outline-transparent',
|
||||
link: 'text-n-blue-11 hover:enabled:underline focus-visible:underline outline-transparent',
|
||||
},
|
||||
ruby: {
|
||||
solid:
|
||||
@@ -133,7 +133,7 @@ const STYLE_CONFIG = {
|
||||
},
|
||||
slate: {
|
||||
solid:
|
||||
'bg-n-solid-3 dark:hover:enabled:bg-n-solid-2 dark:focus-visible:bg-n-solid-2 hover:enabled:bg-n-alpha-2 focus-visible:bg-n-alpha-2 text-n-slate-12 outline-n-container',
|
||||
'bg-n-button-color dark:hover:enabled:bg-n-solid-2 dark:focus-visible:bg-n-solid-2 hover:enabled:bg-n-alpha-2 focus-visible:bg-n-alpha-2 text-n-slate-12 outline-n-container',
|
||||
faded:
|
||||
'bg-n-slate-9/10 text-n-slate-12 hover:enabled:bg-n-slate-9/20 focus-visible:bg-n-slate-9/20 outline-transparent',
|
||||
outline:
|
||||
|
||||
@@ -115,7 +115,7 @@ const handleCreateAssistant = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-background">
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-surface-1">
|
||||
<header class="sticky top-0 z-10 px-6">
|
||||
<div class="w-full max-w-[60rem] mx-auto">
|
||||
<div
|
||||
|
||||
@@ -141,7 +141,7 @@ const onClickCancel = () => {
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAPTAIN.ASSISTANTS.SCENARIOS.ADD.NEW.FORM.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 !text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 !text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="onClickCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -169,7 +169,7 @@ watch(
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAPTAIN.FORM.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -254,7 +254,7 @@ const handleSubmit = async () => {
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAPTAIN.FORM.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -220,7 +220,7 @@ const handleSubmit = async () => {
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAPTAIN.FORM.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -100,7 +100,7 @@ const handleSubmit = async () => {
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAPTAIN.FORM.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -116,7 +116,7 @@ watch(
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAPTAIN.FORM.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -34,7 +34,7 @@ const handleImgClick = () => {
|
||||
<template>
|
||||
<div
|
||||
data-testid="changelog-card"
|
||||
class="flex flex-col justify-between p-3 w-full rounded-lg border shadow-sm transition-all duration-200 border-n-weak bg-n-background text-n-slate-12"
|
||||
class="flex flex-col justify-between p-3 w-full rounded-lg border shadow-sm transition-all duration-200 border-n-weak bg-n-card text-n-slate-12"
|
||||
:class="{
|
||||
'animate-fade-out pointer-events-none': isDismissing,
|
||||
'hover:shadow': isActive,
|
||||
|
||||
@@ -77,12 +77,12 @@ const queryOperatorOptions = computed(() => {
|
||||
{
|
||||
label: t(`FILTER.QUERY_DROPDOWN_LABELS.AND`),
|
||||
value: 'and',
|
||||
icon: h('span', { class: 'i-lucide-ampersands !text-n-blue-text' }),
|
||||
icon: h('span', { class: 'i-lucide-ampersands !text-n-blue-11' }),
|
||||
},
|
||||
{
|
||||
label: t(`FILTER.QUERY_DROPDOWN_LABELS.OR`),
|
||||
value: 'or',
|
||||
icon: h('span', { class: 'i-woot-logic-or !text-n-blue-text' }),
|
||||
icon: h('span', { class: 'i-woot-logic-or !text-n-blue-11' }),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ export function useOperators() {
|
||||
hasInput: !NO_INPUT_OPTS.includes(value),
|
||||
inputOverride: OPS_INPUT_OVERRIDE[value] || null,
|
||||
icon: h('span', {
|
||||
class: `${filterOperatorIcon[value]} !text-n-blue-text`,
|
||||
class: `${filterOperatorIcon[value]} !text-n-blue-11`,
|
||||
}),
|
||||
};
|
||||
return acc;
|
||||
|
||||
@@ -163,7 +163,7 @@ const getInReplyToMessage = parentMessage => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="px-4 bg-n-background">
|
||||
<ul class="px-4 bg-n-surface-1">
|
||||
<slot name="beforeAll" />
|
||||
<template v-for="(message, index) in allMessages" :key="message.id">
|
||||
<slot
|
||||
|
||||
@@ -22,7 +22,7 @@ defineProps({
|
||||
/>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button :label="buttonText" slate class="!text-n-blue-text w-full" />
|
||||
<Button :label="buttonText" slate class="!text-n-blue-11 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -18,12 +18,8 @@ defineProps({
|
||||
/>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button label="Call us" slate class="!text-n-blue-text w-full" />
|
||||
<Button
|
||||
label="Visit our website"
|
||||
slate
|
||||
class="!text-n-blue-text w-full"
|
||||
/>
|
||||
<Button label="Call us" slate class="!text-n-blue-11 w-full" />
|
||||
<Button label="Visit our website" slate class="!text-n-blue-11 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -71,7 +71,7 @@ const pageInfo = computed(() => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex justify-between h-12 w-full max-w-[calc(60rem-3px)] outline outline-n-container outline-1 -outline-offset-1 mx-auto bg-n-solid-2 rounded-xl py-2 ltr:pl-4 rtl:pr-4 ltr:pr-3 rtl:pl-3 items-center before:absolute before:inset-x-0 before:-top-4 before:bg-gradient-to-t before:from-n-background before:from-10% before:dark:from-0% before:to-transparent before:h-4 before:pointer-events-none"
|
||||
class="flex justify-between h-12 w-full max-w-[calc(60rem-3px)] outline outline-n-container outline-1 -outline-offset-1 mx-auto bg-n-solid-2 rounded-xl py-2 ltr:pl-4 rtl:pr-4 ltr:pr-3 rtl:pl-3 items-center before:absolute before:inset-x-0 before:-top-4 before:bg-gradient-to-t before:from-n-surface-1 before:from-10% before:dark:from-0% before:to-transparent before:h-4 before:pointer-events-none"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="min-w-0 text-sm font-normal line-clamp-1 text-n-slate-11">
|
||||
|
||||
@@ -8,6 +8,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
// eslint-disable-next-line vue/no-unused-properties
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -24,10 +25,7 @@ const reauthorizationRequired = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="size-5 grid place-content-center rounded-full bg-n-alpha-2"
|
||||
:class="{ 'bg-n-solid-blue': active }"
|
||||
>
|
||||
<span class="size-5 grid place-content-center rounded-full bg-n-alpha-2">
|
||||
<ChannelIcon :inbox="inbox" class="size-3" />
|
||||
</span>
|
||||
<div class="flex-1 truncate min-w-0">{{ label }}</div>
|
||||
|
||||
@@ -39,7 +39,7 @@ const toggleSidebar = () => {
|
||||
<div
|
||||
v-if="!isConversationRoute"
|
||||
id="mobile-sidebar-launcher"
|
||||
class="fixed bottom-4 ltr:left-4 rtl:right-4 z-40 transition-transform duration-200 ease-in-out block md:hidden"
|
||||
class="fixed bottom-4 ltr:left-4 rtl:right-4 z-40 transition-transform duration-200 ease-out block md:hidden"
|
||||
:class="[
|
||||
{
|
||||
'ltr:translate-x-48 rtl:-translate-x-48': isMobileSidebarOpen,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { h, ref, computed, onMounted } from 'vue';
|
||||
import { provideSidebarContext } from './provider';
|
||||
import { provideSidebarContext, useSidebarResize } from './provider';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
import { useKbd } from 'dashboard/composables/utils/useKbd';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
@@ -8,6 +8,7 @@ import { useStore } from 'vuex';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { useWindowSize, useEventListener } from '@vueuse/core';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
@@ -15,7 +16,9 @@ import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import SidebarGroup from './SidebarGroup.vue';
|
||||
import SidebarProfileMenu from './SidebarProfileMenu.vue';
|
||||
import SidebarChangelogCard from './SidebarChangelogCard.vue';
|
||||
import SidebarChangelogButton from './SidebarChangelogButton.vue';
|
||||
import ChannelLeaf from './ChannelLeaf.vue';
|
||||
import ChannelIcon from 'next/icon/ChannelIcon.vue';
|
||||
import SidebarAccountSwitcher from './SidebarAccountSwitcher.vue';
|
||||
import Logo from 'next/icon/Logo.vue';
|
||||
import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue';
|
||||
@@ -42,6 +45,10 @@ const { t } = useI18n();
|
||||
const isACustomBrandedInstance = useMapGetter(
|
||||
'globalConfig/isACustomBrandedInstance'
|
||||
);
|
||||
const isRTL = useMapGetter('accounts/isRTL');
|
||||
|
||||
const { width: windowWidth } = useWindowSize();
|
||||
const isMobile = computed(() => windowWidth.value < 768);
|
||||
|
||||
const toggleShortcutModalFn = show => {
|
||||
if (show) {
|
||||
@@ -58,11 +65,85 @@ const expandedItem = ref(null);
|
||||
const setExpandedItem = name => {
|
||||
expandedItem.value = expandedItem.value === name ? null : name;
|
||||
};
|
||||
|
||||
const {
|
||||
sidebarWidth,
|
||||
isCollapsed,
|
||||
setSidebarWidth,
|
||||
saveWidth,
|
||||
snapToCollapsed,
|
||||
snapToExpanded,
|
||||
COLLAPSED_THRESHOLD,
|
||||
} = useSidebarResize();
|
||||
|
||||
// On mobile, sidebar is always expanded (flyout mode)
|
||||
const isEffectivelyCollapsed = computed(
|
||||
() => !isMobile.value && isCollapsed.value
|
||||
);
|
||||
|
||||
// Resize handle logic
|
||||
const isResizing = ref(false);
|
||||
const startX = ref(0);
|
||||
const startWidth = ref(0);
|
||||
|
||||
provideSidebarContext({
|
||||
expandedItem,
|
||||
setExpandedItem,
|
||||
isCollapsed: isEffectivelyCollapsed,
|
||||
sidebarWidth,
|
||||
isResizing,
|
||||
});
|
||||
|
||||
// Get clientX from mouse or touch event
|
||||
const getClientX = event =>
|
||||
event.touches ? event.touches[0].clientX : event.clientX;
|
||||
|
||||
const onResizeStart = event => {
|
||||
isResizing.value = true;
|
||||
startX.value = getClientX(event);
|
||||
startWidth.value = sidebarWidth.value;
|
||||
Object.assign(document.body.style, {
|
||||
cursor: 'col-resize',
|
||||
userSelect: 'none',
|
||||
});
|
||||
// Prevent default to avoid scrolling on touch
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const onResizeMove = event => {
|
||||
if (!isResizing.value) return;
|
||||
|
||||
const delta = isRTL.value
|
||||
? startX.value - getClientX(event)
|
||||
: getClientX(event) - startX.value;
|
||||
setSidebarWidth(startWidth.value + delta);
|
||||
};
|
||||
|
||||
const onResizeEnd = () => {
|
||||
if (!isResizing.value) return;
|
||||
|
||||
isResizing.value = false;
|
||||
Object.assign(document.body.style, { cursor: '', userSelect: '' });
|
||||
|
||||
// Snap to collapsed state if below threshold
|
||||
if (sidebarWidth.value < COLLAPSED_THRESHOLD) {
|
||||
snapToCollapsed();
|
||||
} else {
|
||||
saveWidth();
|
||||
}
|
||||
};
|
||||
|
||||
const onResizeHandleDoubleClick = () => {
|
||||
if (isCollapsed.value) snapToExpanded();
|
||||
else snapToCollapsed();
|
||||
};
|
||||
|
||||
// Support both mouse and touch events
|
||||
useEventListener(document, 'mousemove', onResizeMove);
|
||||
useEventListener(document, 'mouseup', onResizeEnd);
|
||||
useEventListener(document, 'touchmove', onResizeMove, { passive: false });
|
||||
useEventListener(document, 'touchend', onResizeEnd);
|
||||
|
||||
const inboxes = useMapGetter('inboxes/getInboxes');
|
||||
const labels = useMapGetter('labels/getLabelsOnSidebar');
|
||||
const teams = useMapGetter('teams/getMyTeams');
|
||||
@@ -192,6 +273,7 @@ const menuItems = computed(() => {
|
||||
children: sortedInboxes.value.map(inbox => ({
|
||||
name: `${inbox.name}-${inbox.id}`,
|
||||
label: inbox.name,
|
||||
icon: h(ChannelIcon, { inbox, class: 'size-[12px]' }),
|
||||
to: accountScopedRoute('inbox_dashboard', { inbox_id: inbox.id }),
|
||||
component: leafProps =>
|
||||
h(ChannelLeaf, {
|
||||
@@ -210,7 +292,7 @@ const menuItems = computed(() => {
|
||||
name: `${label.title}-${label.id}`,
|
||||
label: label.title,
|
||||
icon: h('span', {
|
||||
class: `size-[12px] ring-1 ring-n-alpha-1 dark:ring-white/20 ring-inset rounded-sm`,
|
||||
class: `size-[8px] rounded-sm`,
|
||||
style: { backgroundColor: label.color },
|
||||
}),
|
||||
to: accountScopedRoute('label_conversations', {
|
||||
@@ -338,7 +420,7 @@ const menuItems = computed(() => {
|
||||
name: `${label.title}-${label.id}`,
|
||||
label: label.title,
|
||||
icon: h('span', {
|
||||
class: `size-[12px] ring-1 ring-n-alpha-1 dark:ring-white/20 ring-inset rounded-sm`,
|
||||
class: `size-[8px] rounded-sm`,
|
||||
style: { backgroundColor: label.color },
|
||||
}),
|
||||
to: accountScopedRoute(
|
||||
@@ -604,16 +686,35 @@ const menuItems = computed(() => {
|
||||
closeMobileSidebar,
|
||||
{ ignore: ['#mobile-sidebar-launcher'] },
|
||||
]"
|
||||
class="bg-n-solid-2 rtl:border-l ltr:border-r border-n-weak flex flex-col text-sm pb-1 fixed top-0 ltr:left-0 rtl:right-0 h-full z-40 transition-transform duration-200 ease-in-out md:static w-[200px] basis-[200px] md:flex-shrink-0 md:ltr:translate-x-0 md:rtl:-translate-x-0"
|
||||
class="bg-n-background flex flex-col text-sm pb-0.5 fixed top-0 ltr:left-0 rtl:right-0 h-full z-40 w-[200px] md:w-auto md:relative md:flex-shrink-0 md:ltr:translate-x-0 md:rtl:translate-x-0 ltr:border-r rtl:border-l border-n-weak"
|
||||
:class="[
|
||||
{
|
||||
'shadow-lg md:shadow-none': isMobileSidebarOpen,
|
||||
'ltr:-translate-x-full rtl:translate-x-full': !isMobileSidebarOpen,
|
||||
'transition-transform duration-200 ease-out md:transition-[width]':
|
||||
!isResizing,
|
||||
},
|
||||
]"
|
||||
:style="isMobile ? undefined : { width: `${sidebarWidth}px` }"
|
||||
>
|
||||
<section class="grid gap-2 mt-2 mb-4">
|
||||
<div class="flex gap-2 items-center px-2 min-w-0">
|
||||
<section
|
||||
class="grid"
|
||||
:class="isEffectivelyCollapsed ? 'mt-3 mb-6 gap-4' : 'mt-1 mb-4 gap-2'"
|
||||
>
|
||||
<div
|
||||
class="flex gap-2 items-center min-w-0"
|
||||
:class="{
|
||||
'justify-center px-1': isEffectivelyCollapsed,
|
||||
'px-2': !isEffectivelyCollapsed,
|
||||
}"
|
||||
>
|
||||
<template v-if="isEffectivelyCollapsed">
|
||||
<SidebarAccountSwitcher
|
||||
is-collapsed
|
||||
@show-create-account-modal="emit('showCreateAccountModal')"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="grid flex-shrink-0 place-content-center size-6">
|
||||
<Logo class="size-4" />
|
||||
</div>
|
||||
@@ -622,14 +723,19 @@ const menuItems = computed(() => {
|
||||
class="flex-grow -mx-1 min-w-0"
|
||||
@show-create-account-modal="emit('showCreateAccountModal')"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex gap-2 px-2">
|
||||
<RouterLink
|
||||
:to="{ name: 'search' }"
|
||||
class="flex gap-2 items-center px-2 py-1 w-full h-7 rounded-lg outline outline-1 outline-n-weak bg-n-solid-3 dark:bg-n-black/30"
|
||||
<div
|
||||
class="flex gap-2"
|
||||
:class="isEffectivelyCollapsed ? 'flex-col items-center' : 'px-2'"
|
||||
>
|
||||
<span class="flex-shrink-0 i-lucide-search size-4 text-n-slate-11" />
|
||||
<span class="flex-grow text-left">
|
||||
<RouterLink
|
||||
v-if="!isEffectivelyCollapsed"
|
||||
:to="{ name: 'search' }"
|
||||
class="flex gap-2 items-center px-2 py-1 w-full h-7 rounded-lg outline outline-1 outline-n-weak bg-n-button-color transition-all duration-100 ease-out"
|
||||
>
|
||||
<span class="flex-shrink-0 i-lucide-search size-4 text-n-slate-10" />
|
||||
<span class="flex-grow text-start text-n-slate-10">
|
||||
{{ t('COMBOBOX.SEARCH_PLACEHOLDER') }}
|
||||
</span>
|
||||
<span
|
||||
@@ -638,21 +744,41 @@ const menuItems = computed(() => {
|
||||
{{ searchShortcut }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-else
|
||||
:to="{ name: 'search' }"
|
||||
class="flex items-center justify-center size-8 rounded-lg outline outline-1 outline-n-weak bg-n-button-color transition-all duration-100 ease-out hover:bg-n-alpha-2 dark:hover:bg-n-slate-9/30"
|
||||
:title="t('COMBOBOX.SEARCH_PLACEHOLDER')"
|
||||
>
|
||||
<span class="i-lucide-search size-4 text-n-slate-11" />
|
||||
</RouterLink>
|
||||
<ComposeConversation align-position="right" @close="onComposeClose">
|
||||
<template #trigger="{ toggle }">
|
||||
<template #trigger="{ toggle, isOpen }">
|
||||
<Button
|
||||
icon="i-lucide-pen-line"
|
||||
color="slate"
|
||||
size="sm"
|
||||
class="!h-7 !bg-n-solid-3 dark:!bg-n-black/30 !outline-n-weak !text-n-slate-11"
|
||||
class="dark:hover:!bg-n-slate-9/30"
|
||||
:class="[
|
||||
isEffectivelyCollapsed
|
||||
? '!size-8 !outline-n-weak !text-n-slate-11'
|
||||
: '!h-7 !outline-n-weak !text-n-slate-11',
|
||||
{ '!bg-n-alpha-2 dark:!bg-n-slate-9/30': isOpen },
|
||||
]"
|
||||
@click="onComposeOpen(toggle)"
|
||||
/>
|
||||
</template>
|
||||
</ComposeConversation>
|
||||
</div>
|
||||
</section>
|
||||
<nav class="grid overflow-y-scroll flex-grow gap-2 px-2 pb-5 no-scrollbar">
|
||||
<ul class="flex flex-col gap-1.5 m-0 list-none">
|
||||
<nav
|
||||
class="grid overflow-y-scroll flex-grow gap-2 pb-5 no-scrollbar min-w-0"
|
||||
:class="isEffectivelyCollapsed ? 'px-1' : 'px-2'"
|
||||
>
|
||||
<ul
|
||||
class="flex flex-col gap-1 m-0 list-none min-w-0"
|
||||
:class="{ 'items-center': isEffectivelyCollapsed }"
|
||||
>
|
||||
<SidebarGroup
|
||||
v-for="item in menuItems"
|
||||
:key="item.name"
|
||||
@@ -664,18 +790,43 @@ const menuItems = computed(() => {
|
||||
class="flex relative flex-col flex-shrink-0 gap-1 justify-between items-center"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-none absolute inset-x-0 -top-[31px] h-8 bg-gradient-to-t from-n-solid-2 to-transparent"
|
||||
class="pointer-events-none absolute inset-x-0 -top-[1.938rem] h-8 bg-gradient-to-t from-n-background to-transparent"
|
||||
/>
|
||||
<SidebarChangelogCard
|
||||
v-if="isOnChatwootCloud && !isACustomBrandedInstance"
|
||||
v-if="
|
||||
isOnChatwootCloud &&
|
||||
!isACustomBrandedInstance &&
|
||||
!isEffectivelyCollapsed
|
||||
"
|
||||
/>
|
||||
<SidebarChangelogButton
|
||||
v-if="
|
||||
isOnChatwootCloud &&
|
||||
!isACustomBrandedInstance &&
|
||||
isEffectivelyCollapsed
|
||||
"
|
||||
/>
|
||||
<div
|
||||
class="p-1 flex-shrink-0 flex w-full justify-between z-10 gap-2 items-center border-t border-n-weak shadow-[0px_-2px_4px_0px_rgba(27,28,29,0.02)]"
|
||||
class="p-1 flex-shrink-0 flex w-full z-50 gap-2 items-center border-t border-n-weak shadow-[0px_-2px_4px_0px_rgba(27,28,29,0.02)]"
|
||||
:class="isEffectivelyCollapsed ? 'justify-center' : 'justify-between'"
|
||||
>
|
||||
<SidebarProfileMenu
|
||||
:is-collapsed="isEffectivelyCollapsed"
|
||||
@open-key-shortcut-modal="emit('openKeyShortcutModal')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Resize Handle (desktop only) -->
|
||||
<div
|
||||
class="hidden md:block absolute top-0 h-full w-1 cursor-col-resize z-40 ltr:right-0 rtl:left-0 group"
|
||||
@mousedown="onResizeStart"
|
||||
@touchstart="onResizeStart"
|
||||
@dblclick="onResizeHandleDoubleClick"
|
||||
>
|
||||
<div
|
||||
class="absolute top-0 h-full w-px ltr:right-0 rtl:left-0 bg-transparent group-hover:bg-n-brand transition-colors"
|
||||
:class="{ 'bg-n-brand': isResizing }"
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import ButtonNext from 'next/button/Button.vue';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import Logo from 'next/icon/Logo.vue';
|
||||
|
||||
import {
|
||||
DropdownContainer,
|
||||
@@ -13,6 +14,13 @@ import {
|
||||
DropdownItem,
|
||||
} from 'next/dropdown-menu/base';
|
||||
|
||||
defineProps({
|
||||
isCollapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['showCreateAccountModal']);
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -45,7 +53,19 @@ const emitNewAccount = () => {
|
||||
<template>
|
||||
<DropdownContainer>
|
||||
<template #trigger="{ toggle, isOpen }">
|
||||
<!-- Collapsed view: Logo trigger -->
|
||||
<button
|
||||
v-if="isCollapsed"
|
||||
class="grid flex-shrink-0 place-content-center p-2 rounded-lg cursor-pointer hover:bg-n-alpha-1"
|
||||
:class="{ 'bg-n-alpha-1': isOpen }"
|
||||
:title="currentAccount.name"
|
||||
@click="toggle"
|
||||
>
|
||||
<Logo class="size-7" />
|
||||
</button>
|
||||
<!-- Expanded view: Account name trigger -->
|
||||
<button
|
||||
v-else
|
||||
id="sidebar-account-switcher"
|
||||
:data-account-id="accountId"
|
||||
aria-haspopup="listbox"
|
||||
@@ -73,7 +93,10 @@ const emitNewAccount = () => {
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
<DropdownBody v-if="showAccountSwitcher" class="min-w-80 z-50">
|
||||
<DropdownBody
|
||||
v-if="showAccountSwitcher || isCollapsed"
|
||||
class="min-w-80 z-50"
|
||||
>
|
||||
<DropdownSection :title="t('SIDEBAR_ITEMS.SWITCH_ACCOUNT')">
|
||||
<DropdownItem
|
||||
v-for="account in sortedCurrentUserAccounts"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup>
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import SidebarChangelogCard from './SidebarChangelogCard.vue';
|
||||
|
||||
const [isOpen, toggleOpen] = useToggle(false);
|
||||
const changelogCard = useTemplateRef('changelogCard');
|
||||
|
||||
const isLoading = computed(() => changelogCard.value?.isLoading || false);
|
||||
const hasArticles = computed(
|
||||
() => changelogCard.value?.unDismissedPosts?.length > 0
|
||||
);
|
||||
const shouldShowButton = computed(() => !isLoading.value && hasArticles.value);
|
||||
|
||||
const closePopover = () => {
|
||||
if (isOpen.value) {
|
||||
toggleOpen(false);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-on-click-outside="closePopover" class="relative mb-2">
|
||||
<Button
|
||||
v-if="shouldShowButton"
|
||||
icon="i-lucide-sparkles"
|
||||
ghost
|
||||
slate
|
||||
:class="{ '!bg-n-alpha-2 dark:!bg-n-slate-9/30': isOpen }"
|
||||
@click="toggleOpen()"
|
||||
/>
|
||||
|
||||
<!-- Always render card so it can fetch data, control visibility with v-show -->
|
||||
<div
|
||||
v-show="isOpen && hasArticles"
|
||||
class="absolute ltr:left-full rtl:right-full bottom-0 ltr:ml-4 rtl:mr-4 z-40 bg-transparent w-52"
|
||||
>
|
||||
<SidebarChangelogCard
|
||||
ref="changelogCard"
|
||||
class="[&>div]:!pb-0 [&>div]:!px-0 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -4,6 +4,10 @@ import GroupedStackedChangelogCard from 'dashboard/components-next/changelog-car
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import changelogAPI from 'dashboard/api/changelog';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const MAX_DISMISSED_SLUGS = 5;
|
||||
|
||||
const { uiSettings, updateUISettings } = useUISettings();
|
||||
@@ -90,6 +94,11 @@ const handleImgClick = ({ index }) => {
|
||||
handleReadMore();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
isLoading,
|
||||
unDismissedPosts,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
fetchChangelog();
|
||||
});
|
||||
@@ -98,6 +107,7 @@ onMounted(() => {
|
||||
<template>
|
||||
<GroupedStackedChangelogCard
|
||||
v-if="unDismissedPosts.length > 0"
|
||||
v-bind="$attrs"
|
||||
:posts="unDismissedPosts"
|
||||
:current-index="currentIndex"
|
||||
:dismissing-slugs="dismissingCards"
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
<script setup>
|
||||
import { computed, ref, onMounted, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useSidebarContext } from './provider';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import TeleportWithDirection from 'dashboard/components-next/TeleportWithDirection.vue';
|
||||
|
||||
const props = defineProps({
|
||||
label: { type: String, required: true },
|
||||
children: { type: Array, default: () => [] },
|
||||
activeChild: { type: Object, default: undefined },
|
||||
triggerRect: { type: Object, default: () => ({ top: 0, left: 0 }) },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'mouseenter', 'mouseleave']);
|
||||
|
||||
const router = useRouter();
|
||||
const { isAllowed, sidebarWidth } = useSidebarContext();
|
||||
|
||||
const expandedSubGroup = ref(null);
|
||||
const popoverRef = ref(null);
|
||||
const topPosition = ref(0);
|
||||
const isRTL = useMapGetter('accounts/isRTL');
|
||||
const skipTransition = ref(true);
|
||||
|
||||
const toggleSubGroup = name => {
|
||||
expandedSubGroup.value = expandedSubGroup.value === name ? null : name;
|
||||
};
|
||||
|
||||
const navigateAndClose = to => {
|
||||
router.push(to);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const isActive = child => props.activeChild?.name === child.name;
|
||||
|
||||
const getAccessibleSubChildren = children =>
|
||||
children.filter(c => isAllowed(c.to));
|
||||
|
||||
const renderIcon = icon => ({
|
||||
component: typeof icon === 'object' ? icon : Icon,
|
||||
props: typeof icon === 'string' ? { icon } : null,
|
||||
});
|
||||
|
||||
const transition = computed(() =>
|
||||
skipTransition.value
|
||||
? {}
|
||||
: {
|
||||
enterActiveClass: 'transition-all duration-200 ease-out',
|
||||
enterFromClass: 'opacity-0 -translate-y-2 max-h-0',
|
||||
enterToClass: 'opacity-100 translate-y-0 max-h-96',
|
||||
leaveActiveClass: 'transition-all duration-150 ease-in',
|
||||
leaveFromClass: 'opacity-100 translate-y-0 max-h-96',
|
||||
leaveToClass: 'opacity-0 -translate-y-2 max-h-0',
|
||||
}
|
||||
);
|
||||
|
||||
const accessibleChildren = computed(() => {
|
||||
return props.children.filter(child => {
|
||||
if (child.children) {
|
||||
return child.children.some(subChild => isAllowed(subChild.to));
|
||||
}
|
||||
return child.to && isAllowed(child.to);
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
|
||||
// Auto-expand subgroup if active child is inside it
|
||||
if (props.activeChild) {
|
||||
const parentGroup = props.children.find(child =>
|
||||
child.children?.some(subChild => subChild.name === props.activeChild.name)
|
||||
);
|
||||
if (parentGroup) {
|
||||
expandedSubGroup.value = parentGroup.name;
|
||||
// Wait for the subgroup expansion to render before measuring height
|
||||
await nextTick();
|
||||
}
|
||||
}
|
||||
|
||||
if (!props.triggerRect) return;
|
||||
|
||||
const viewportHeight = window.innerHeight;
|
||||
const popoverHeight = popoverRef.value?.offsetHeight || 300;
|
||||
const { top: triggerTop } = props.triggerRect;
|
||||
|
||||
// Adjust position if popover would overflow viewport
|
||||
topPosition.value =
|
||||
triggerTop + popoverHeight > viewportHeight - 20
|
||||
? Math.max(20, viewportHeight - popoverHeight - 20)
|
||||
: triggerTop;
|
||||
|
||||
await nextTick();
|
||||
skipTransition.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TeleportWithDirection>
|
||||
<div
|
||||
ref="popoverRef"
|
||||
class="fixed z-[100] min-w-[200px] max-w-[280px]"
|
||||
:style="{
|
||||
[isRTL ? 'right' : 'left']: `${sidebarWidth + 8}px`,
|
||||
top: `${topPosition}px`,
|
||||
}"
|
||||
@mouseenter="emit('mouseenter')"
|
||||
@mouseleave="emit('mouseleave')"
|
||||
>
|
||||
<div
|
||||
class="bg-n-alpha-3 backdrop-blur-[100px] outline outline-1 -outline-offset-1 w-56 outline-n-weak rounded-xl shadow-lg py-2 px-2"
|
||||
>
|
||||
<div
|
||||
class="px-2 py-1.5 text-xs font-medium text-n-slate-11 uppercase tracking-wider border-b border-n-weak mb-1"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
<ul
|
||||
class="m-0 p-0 list-none max-h-[400px] overflow-y-auto no-scrollbar"
|
||||
>
|
||||
<template v-for="child in accessibleChildren" :key="child.name">
|
||||
<!-- SubGroup with children -->
|
||||
<li v-if="child.children" class="py-0.5">
|
||||
<button
|
||||
class="flex items-center gap-2 px-2 py-1.5 w-full rounded-lg text-n-slate-11 hover:bg-n-alpha-2 transition-colors duration-150 ease-out text-left rtl:text-right"
|
||||
@click="toggleSubGroup(child.name)"
|
||||
>
|
||||
<Icon
|
||||
v-if="child.icon"
|
||||
:icon="child.icon"
|
||||
class="size-4 flex-shrink-0"
|
||||
/>
|
||||
<span class="flex-1 truncate text-sm">{{ child.label }}</span>
|
||||
<span
|
||||
class="size-3 transition-transform i-lucide-chevron-down"
|
||||
:class="{
|
||||
'rotate-180': expandedSubGroup === child.name,
|
||||
}"
|
||||
/>
|
||||
</button>
|
||||
<Transition v-bind="transition">
|
||||
<ul
|
||||
v-if="expandedSubGroup === child.name"
|
||||
class="m-0 p-0 list-none ltr:pl-4 rtl:pr-4 mt-1 overflow-hidden"
|
||||
>
|
||||
<li
|
||||
v-for="subChild in getAccessibleSubChildren(child.children)"
|
||||
:key="subChild.name"
|
||||
class="py-0.5"
|
||||
>
|
||||
<button
|
||||
class="flex items-center gap-2 px-2 py-1.5 w-full rounded-lg text-sm text-left rtl:text-right transition-colors duration-150 ease-out"
|
||||
:class="{
|
||||
'text-n-slate-12 bg-n-alpha-2': isActive(subChild),
|
||||
'text-n-slate-11 hover:bg-n-alpha-2':
|
||||
!isActive(subChild),
|
||||
}"
|
||||
@click="navigateAndClose(subChild.to)"
|
||||
>
|
||||
<component
|
||||
:is="renderIcon(subChild.icon).component"
|
||||
v-if="subChild.icon"
|
||||
v-bind="renderIcon(subChild.icon).props"
|
||||
class="size-4 flex-shrink-0"
|
||||
/>
|
||||
<span class="flex-1 truncate">{{ subChild.label }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</li>
|
||||
<!-- Direct child item -->
|
||||
<li v-else class="py-0.5">
|
||||
<button
|
||||
class="flex items-center gap-2 px-2 py-1.5 w-full rounded-lg text-sm text-left rtl:text-right transition-colors duration-150 ease-out"
|
||||
:class="{
|
||||
'text-n-slate-12 bg-n-alpha-2': isActive(child),
|
||||
'text-n-slate-11 hover:bg-n-alpha-2': !isActive(child),
|
||||
}"
|
||||
@click="navigateAndClose(child.to)"
|
||||
>
|
||||
<component
|
||||
:is="renderIcon(child.icon).component"
|
||||
v-if="child.icon"
|
||||
v-bind="renderIcon(child.icon).props"
|
||||
class="size-4 flex-shrink-0"
|
||||
/>
|
||||
<span class="flex-1 truncate">{{ child.label }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</TeleportWithDirection>
|
||||
</template>
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, watch, nextTick } from 'vue';
|
||||
import { useSidebarContext } from './provider';
|
||||
import { computed, onMounted, onUnmounted, watch, nextTick, ref } from 'vue';
|
||||
import { useSidebarContext, usePopoverState } from './provider';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import Policy from 'dashboard/components/policy.vue';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import SidebarGroupHeader from './SidebarGroupHeader.vue';
|
||||
import SidebarGroupLeaf from './SidebarGroupLeaf.vue';
|
||||
import SidebarSubGroup from './SidebarSubGroup.vue';
|
||||
import SidebarGroupEmptyLeaf from './SidebarGroupEmptyLeaf.vue';
|
||||
import SidebarCollapsedPopover from './SidebarCollapsedPopover.vue';
|
||||
|
||||
const props = defineProps({
|
||||
name: { type: String, required: true },
|
||||
@@ -25,8 +27,18 @@ const {
|
||||
resolvePermissions,
|
||||
resolveFeatureFlag,
|
||||
isAllowed,
|
||||
isCollapsed,
|
||||
isResizing,
|
||||
} = useSidebarContext();
|
||||
|
||||
const {
|
||||
activePopover,
|
||||
setActivePopover,
|
||||
closeActivePopover,
|
||||
scheduleClose,
|
||||
cancelClose,
|
||||
} = usePopoverState();
|
||||
|
||||
const navigableChildren = computed(() => {
|
||||
return props.children?.flatMap(child => child.children || child) || [];
|
||||
});
|
||||
@@ -39,6 +51,54 @@ const hasChildren = computed(
|
||||
() => Array.isArray(props.children) && props.children.length > 0
|
||||
);
|
||||
|
||||
// Use shared popover state - only one popover can be open at a time
|
||||
const isPopoverOpen = computed(() => activePopover.value === props.name);
|
||||
const triggerRef = ref(null);
|
||||
const triggerRect = ref({ top: 0, left: 0, bottom: 0, right: 0 });
|
||||
|
||||
const openPopover = () => {
|
||||
if (triggerRef.value) {
|
||||
const rect = triggerRef.value.getBoundingClientRect();
|
||||
triggerRect.value = {
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
bottom: rect.bottom,
|
||||
right: rect.right,
|
||||
};
|
||||
}
|
||||
setActivePopover(props.name);
|
||||
};
|
||||
|
||||
const closePopover = () => {
|
||||
if (activePopover.value === props.name) {
|
||||
closeActivePopover();
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (!hasChildren.value || isResizing.value) return;
|
||||
cancelClose();
|
||||
openPopover();
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (!hasChildren.value) return;
|
||||
scheduleClose(200);
|
||||
};
|
||||
|
||||
const handlePopoverMouseEnter = () => {
|
||||
cancelClose();
|
||||
};
|
||||
|
||||
const handlePopoverMouseLeave = () => {
|
||||
scheduleClose(100);
|
||||
};
|
||||
|
||||
// Close popover when mouse leaves the window
|
||||
const handleWindowBlur = () => {
|
||||
closeActivePopover();
|
||||
};
|
||||
|
||||
const accessibleItems = computed(() => {
|
||||
if (!hasChildren.value) return [];
|
||||
return props.children.filter(child => {
|
||||
@@ -107,6 +167,13 @@ const hasActiveChild = computed(() => {
|
||||
return activeChild.value !== undefined;
|
||||
});
|
||||
|
||||
const handleCollapsedClick = () => {
|
||||
if (hasChildren.value && hasAccessibleChildren.value) {
|
||||
const firstItem = accessibleItems.value[0];
|
||||
router.push(firstItem.to);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleTrigger = () => {
|
||||
if (
|
||||
hasAccessibleChildren.value &&
|
||||
@@ -125,6 +192,13 @@ onMounted(async () => {
|
||||
if (hasActiveChild.value) {
|
||||
setExpandedItem(props.name);
|
||||
}
|
||||
window.addEventListener('blur', handleWindowBlur);
|
||||
document.addEventListener('mouseleave', handleWindowBlur);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('blur', handleWindowBlur);
|
||||
document.removeEventListener('mouseleave', handleWindowBlur);
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -145,8 +219,44 @@ watch(
|
||||
:permissions="resolvePermissions(to)"
|
||||
:feature-flag="resolveFeatureFlag(to)"
|
||||
as="li"
|
||||
class="grid gap-1 text-sm cursor-pointer select-none"
|
||||
class="grid gap-1 text-sm cursor-pointer select-none min-w-0"
|
||||
>
|
||||
<!-- Collapsed State -->
|
||||
<template v-if="isCollapsed">
|
||||
<div
|
||||
class="relative"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<component
|
||||
:is="to && !hasChildren ? 'router-link' : 'button'"
|
||||
ref="triggerRef"
|
||||
:to="to && !hasChildren ? to : undefined"
|
||||
type="button"
|
||||
class="flex items-center justify-center size-10 rounded-lg"
|
||||
:class="{
|
||||
'text-n-slate-12 bg-n-alpha-2': isActive || hasActiveChild,
|
||||
'text-n-slate-11 hover:bg-n-alpha-2': !isActive && !hasActiveChild,
|
||||
}"
|
||||
:title="label"
|
||||
@click="hasChildren ? handleCollapsedClick() : undefined"
|
||||
>
|
||||
<Icon v-if="icon" :icon="icon" class="size-4" />
|
||||
</component>
|
||||
<SidebarCollapsedPopover
|
||||
v-if="hasChildren && isPopoverOpen"
|
||||
:label="label"
|
||||
:children="children"
|
||||
:active-child="activeChild"
|
||||
:trigger-rect="triggerRect"
|
||||
@close="closePopover"
|
||||
@mouseenter="handlePopoverMouseEnter"
|
||||
@mouseleave="handlePopoverMouseLeave"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Expanded State -->
|
||||
<template v-else>
|
||||
<SidebarGroupHeader
|
||||
:icon
|
||||
:name
|
||||
@@ -162,7 +272,7 @@ watch(
|
||||
<ul
|
||||
v-if="hasChildren"
|
||||
v-show="isExpanded || hasActiveChild"
|
||||
class="grid m-0 list-none sidebar-group-children"
|
||||
class="grid m-0 list-none sidebar-group-children min-w-0"
|
||||
>
|
||||
<template v-for="child in children" :key="child.name">
|
||||
<SidebarSubGroup
|
||||
@@ -184,6 +294,7 @@ watch(
|
||||
<ul v-else-if="isExpandable && isExpanded">
|
||||
<SidebarGroupEmptyLeaf />
|
||||
</ul>
|
||||
</template>
|
||||
</Policy>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@ const count = computed(() =>
|
||||
<template>
|
||||
<component
|
||||
:is="to ? 'router-link' : 'div'"
|
||||
class="flex items-center gap-2 px-2 py-1.5 rounded-lg h-8 min-w-0"
|
||||
class="flex items-center gap-2 px-1.5 py-1 rounded-lg h-8 min-w-0"
|
||||
role="button"
|
||||
draggable="false"
|
||||
:to="to"
|
||||
:title="label"
|
||||
:class="{
|
||||
'text-n-blue-text bg-n-alpha-2 font-medium': isActive && !hasActiveChild,
|
||||
'text-n-slate-12 bg-n-alpha-2 font-medium': isActive && !hasActiveChild,
|
||||
'text-n-slate-12 font-medium': hasActiveChild,
|
||||
'text-n-slate-11 hover:bg-n-alpha-2': !isActive && !hasActiveChild,
|
||||
}"
|
||||
@@ -45,15 +45,21 @@ const count = computed(() =>
|
||||
class="size-2 -top-px ltr:-right-px rtl:-left-px bg-n-brand absolute rounded-full border border-n-solid-2"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 flex-grow min-w-0">
|
||||
<span class="text-sm font-medium leading-5 truncate">
|
||||
<div class="flex items-center gap-1.5 flex-grow min-w-0 flex-1">
|
||||
<span
|
||||
class="truncate"
|
||||
:class="{
|
||||
'text-body-main': !isActive,
|
||||
'font-medium text-sm': isActive || hasActiveChild,
|
||||
}"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
<span
|
||||
v-if="dynamicCount && !expandable"
|
||||
class="rounded-md capitalize text-xs leading-5 font-medium text-center outline outline-1 px-1 flex-shrink-0"
|
||||
:class="{
|
||||
'text-n-blue-text outline-n-slate-6': isActive,
|
||||
'text-n-slate-12 outline-n-slate-6': isActive,
|
||||
'text-n-slate-11 outline-n-strong': !isActive,
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -25,15 +25,15 @@ const shouldRenderComponent = computed(() => {
|
||||
:permissions="resolvePermissions(to)"
|
||||
:feature-flag="resolveFeatureFlag(to)"
|
||||
as="li"
|
||||
class="py-0.5 ltr:pl-3 rtl:pr-3 rtl:mr-3 ltr:ml-3 relative text-n-slate-11 child-item before:bg-n-slate-4 after:bg-transparent after:border-n-slate-4 before:left-0 rtl:before:right-0"
|
||||
class="py-0.5 ltr:pl-2 rtl:pr-2 rtl:mr-3 ltr:ml-3 relative text-n-slate-11 child-item before:bg-n-slate-4 after:bg-transparent after:border-n-slate-4 before:left-0 rtl:before:right-0 min-w-0"
|
||||
>
|
||||
<component
|
||||
:is="to ? 'router-link' : 'div'"
|
||||
:to="to"
|
||||
:title="label"
|
||||
class="flex h-8 items-center gap-2 px-2 py-1 rounded-lg max-w-[9.438rem] hover:bg-gradient-to-r from-transparent via-n-slate-3/70 to-n-slate-3/70 group"
|
||||
class="flex h-8 items-center gap-2 px-2 py-1 rounded-lg hover:bg-gradient-to-r from-transparent via-n-slate-3/70 to-n-slate-3/70 group min-w-0"
|
||||
:class="{
|
||||
'text-n-blue-text bg-n-alpha-2 active': active,
|
||||
'text-n-slate-12 bg-n-alpha-2 active': active,
|
||||
}"
|
||||
>
|
||||
<component
|
||||
|
||||
@@ -15,6 +15,10 @@ import {
|
||||
} from 'next/dropdown-menu/base';
|
||||
import CustomBrandPolicyWrapper from '../../components/CustomBrandPolicyWrapper.vue';
|
||||
|
||||
defineProps({
|
||||
isCollapsed: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'openKeyShortcutModal']);
|
||||
|
||||
defineOptions({
|
||||
@@ -120,11 +124,19 @@ const allowedMenuItems = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownContainer class="relative w-full min-w-0" @close="emit('close')">
|
||||
<DropdownContainer
|
||||
class="relative min-w-0"
|
||||
:class="isCollapsed ? 'w-auto' : 'w-full'"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<template #trigger="{ toggle, isOpen }">
|
||||
<button
|
||||
class="flex gap-2 items-center p-1 w-full text-left rounded-lg cursor-pointer hover:bg-n-alpha-1"
|
||||
:class="{ 'bg-n-alpha-1': isOpen }"
|
||||
class="flex gap-2 items-center p-1 text-left rounded-lg cursor-pointer hover:bg-n-alpha-1"
|
||||
:class="[
|
||||
{ 'bg-n-alpha-1': isOpen },
|
||||
isCollapsed ? 'justify-center' : 'w-full',
|
||||
]"
|
||||
:title="isCollapsed ? currentUser.available_name : undefined"
|
||||
@click="toggle"
|
||||
>
|
||||
<Avatar
|
||||
@@ -135,7 +147,7 @@ const allowedMenuItems = computed(() => {
|
||||
class="flex-shrink-0"
|
||||
rounded-full
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<div v-if="!isCollapsed" class="min-w-0">
|
||||
<div class="text-sm font-medium leading-4 truncate text-n-slate-12">
|
||||
{{ currentUser.available_name }}
|
||||
</div>
|
||||
|
||||
@@ -48,11 +48,15 @@ useEventListener(scrollableContainer, 'scroll', () => {
|
||||
:icon
|
||||
class="my-1"
|
||||
/>
|
||||
<ul v-if="children.length" class="m-0 list-none reset-base relative group">
|
||||
<ul
|
||||
v-if="children.length"
|
||||
class="m-0 list-none reset-base relative group min-w-0"
|
||||
>
|
||||
<!-- Each element has h-8, which is 32px, we will show 7 items with one hidden at the end,
|
||||
which is 14rem. Then we add 16px so that we have some text visible from the next item -->
|
||||
<div
|
||||
ref="scrollableContainer"
|
||||
class="min-w-0"
|
||||
:class="{
|
||||
'max-h-[calc(14rem+16px)] overflow-y-scroll no-scrollbar': isScrollable,
|
||||
}"
|
||||
@@ -68,7 +72,7 @@ useEventListener(scrollableContainer, 'scroll', () => {
|
||||
<div
|
||||
v-if="isScrollable && isExpanded"
|
||||
v-show="!scrollEnd"
|
||||
class="absolute bg-gradient-to-t from-n-solid-2 w-full h-12 to-transparent -bottom-1 pointer-events-none flex items-end justify-end px-2 animate-fade-in-up"
|
||||
class="absolute bg-gradient-to-t from-n-background w-full h-12 to-transparent -bottom-1 pointer-events-none flex items-end justify-end px-2 animate-fade-in-up"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
|
||||
@@ -1,9 +1,87 @@
|
||||
import { inject, provide } from 'vue';
|
||||
import { inject, provide, ref, computed } from 'vue';
|
||||
import { usePolicy } from 'dashboard/composables/usePolicy';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
|
||||
const SidebarControl = Symbol('SidebarControl');
|
||||
|
||||
const DEFAULT_WIDTH = 200;
|
||||
const MIN_WIDTH = 56;
|
||||
const COLLAPSED_THRESHOLD = 160;
|
||||
const MAX_WIDTH = 320;
|
||||
|
||||
// Shared state for active popover (only one can be open at a time)
|
||||
const activePopover = ref(null);
|
||||
let globalCloseTimeout = null;
|
||||
|
||||
export function useSidebarResize() {
|
||||
const { uiSettings, updateUISettings } = useUISettings();
|
||||
|
||||
const sidebarWidth = ref(uiSettings.value.sidebar_width || DEFAULT_WIDTH);
|
||||
const isCollapsed = computed(() => sidebarWidth.value < COLLAPSED_THRESHOLD);
|
||||
|
||||
const setSidebarWidth = width => {
|
||||
sidebarWidth.value = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, width));
|
||||
};
|
||||
|
||||
const saveWidth = () => {
|
||||
updateUISettings({ sidebar_width: sidebarWidth.value });
|
||||
};
|
||||
|
||||
const snapToCollapsed = () => {
|
||||
sidebarWidth.value = MIN_WIDTH;
|
||||
updateUISettings({ sidebar_width: MIN_WIDTH });
|
||||
};
|
||||
|
||||
const snapToExpanded = () => {
|
||||
sidebarWidth.value = DEFAULT_WIDTH;
|
||||
updateUISettings({ sidebar_width: DEFAULT_WIDTH });
|
||||
};
|
||||
|
||||
return {
|
||||
sidebarWidth,
|
||||
isCollapsed,
|
||||
setSidebarWidth,
|
||||
saveWidth,
|
||||
snapToCollapsed,
|
||||
snapToExpanded,
|
||||
MIN_WIDTH,
|
||||
MAX_WIDTH,
|
||||
COLLAPSED_THRESHOLD,
|
||||
DEFAULT_WIDTH,
|
||||
};
|
||||
}
|
||||
|
||||
export function usePopoverState() {
|
||||
const setActivePopover = name => {
|
||||
clearTimeout(globalCloseTimeout);
|
||||
activePopover.value = name;
|
||||
};
|
||||
|
||||
const closeActivePopover = () => {
|
||||
activePopover.value = null;
|
||||
};
|
||||
|
||||
const scheduleClose = (delay = 150) => {
|
||||
clearTimeout(globalCloseTimeout);
|
||||
globalCloseTimeout = setTimeout(() => {
|
||||
closeActivePopover();
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const cancelClose = () => {
|
||||
clearTimeout(globalCloseTimeout);
|
||||
};
|
||||
|
||||
return {
|
||||
activePopover,
|
||||
setActivePopover,
|
||||
closeActivePopover,
|
||||
scheduleClose,
|
||||
cancelClose,
|
||||
};
|
||||
}
|
||||
|
||||
export function useSidebarContext() {
|
||||
const context = inject(SidebarControl, null);
|
||||
if (context === null) {
|
||||
@@ -11,7 +89,6 @@ export function useSidebarContext() {
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { shouldShow } = usePolicy();
|
||||
|
||||
const resolvePath = to => {
|
||||
|
||||
@@ -27,7 +27,7 @@ const updateValue = () => {
|
||||
>
|
||||
<span class="sr-only">{{ t('SWITCH.TOGGLE') }}</span>
|
||||
<span
|
||||
class="absolute top-0.5 left-0.5 h-3 w-3 transform rounded-full shadow-sm transition-transform duration-200 ease-in-out"
|
||||
class="absolute top-0.5 left-0.5 h-3 w-3 transform rounded-full shadow-sm transition-transform duration-200 ease-out"
|
||||
:class="
|
||||
modelValue
|
||||
? 'translate-x-3 bg-n-background'
|
||||
|
||||
@@ -84,7 +84,7 @@ const showDivider = index => {
|
||||
class="relative z-10 px-4 truncate py-1.5 text-sm border-0 outline-1 outline-transparent rounded-lg transition-all duration-200 ease-out hover:text-n-brand active:scale-[1.02]"
|
||||
:class="[
|
||||
activeTab === index
|
||||
? 'text-n-blue-text scale-100'
|
||||
? 'text-n-blue-11 scale-100'
|
||||
: 'text-n-slate-10 scale-[0.98]',
|
||||
]"
|
||||
@click="selectTab(index)"
|
||||
|
||||
@@ -47,7 +47,7 @@ const onToggle = () => {
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<slot name="button" />
|
||||
<div class="flex justify-end w-3 text-n-blue-text cursor-pointer">
|
||||
<div class="flex justify-end w-3 text-n-blue-11 cursor-pointer">
|
||||
<fluent-icon v-if="isOpen" size="24" icon="subtract" type="solid" />
|
||||
<fluent-icon v-else size="24" icon="add" type="solid" />
|
||||
</div>
|
||||
@@ -55,7 +55,7 @@ const onToggle = () => {
|
||||
</button>
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="bg-n-background outline outline-1 outline-n-weak -mt-[-1px] border-t-0 rounded-br-lg rounded-bl-lg"
|
||||
class="outline outline-1 outline-n-weak -mt-[-1px] border-t-0 rounded-br-lg rounded-bl-lg"
|
||||
:class="compact ? 'p-0' : 'px-2 py-4'"
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -902,7 +902,7 @@ watch(conversationFilters, (newVal, oldVal) => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-shrink-0 bg-n-solid-1 conversations-list-wrap"
|
||||
class="flex flex-col flex-shrink-0 conversations-list-wrap bg-n-surface-1"
|
||||
:class="[
|
||||
{ hidden: !showConversationList },
|
||||
isOnExpandedLayout ? 'basis-full' : 'w-[340px] 2xl:w-[412px]',
|
||||
|
||||
@@ -57,7 +57,7 @@ const toggleConversationLayout = () => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between gap-2 px-3 h-12"
|
||||
class="flex items-center justify-between gap-2 px-3 h-[3.25rem]"
|
||||
:class="{
|
||||
'border-b border-n-strong': hasAppliedFiltersOrActiveFolders,
|
||||
}"
|
||||
|
||||
@@ -133,7 +133,7 @@ onMounted(() => {
|
||||
<div
|
||||
v-if="shouldShowCopilotPanel"
|
||||
v-on-click-outside="() => closeCopilotPanel()"
|
||||
class="bg-n-background h-full overflow-hidden flex-col fixed top-0 ltr:right-0 rtl:left-0 z-40 w-full max-w-sm transition-transform duration-300 ease-in-out md:static md:w-[320px] md:min-w-[320px] ltr:border-l rtl:border-r border-n-weak 2xl:min-w-[360px] 2xl:w-[360px] shadow-lg md:shadow-none"
|
||||
class="bg-n-surface-2 h-full overflow-hidden flex-col fixed top-0 ltr:right-0 rtl:left-0 z-40 w-full max-w-sm transition-transform duration-300 ease-in-out md:static md:w-[320px] md:min-w-[320px] ltr:border-l rtl:border-r border-n-weak 2xl:min-w-[360px] 2xl:w-[360px] shadow-lg md:shadow-none"
|
||||
:class="[
|
||||
{
|
||||
'md:flex': shouldShowCopilotPanel,
|
||||
|
||||
@@ -116,7 +116,7 @@ const dayClasses = day => ({
|
||||
(isInRange(day) || isHoveringInRange(day)) &&
|
||||
!isSelectedStartOrEndDate(day) &&
|
||||
isInCurrentMonth(day),
|
||||
'outline outline-1 outline-n-blue-8 -outline-offset-1 !text-n-blue-text':
|
||||
'outline outline-1 outline-n-blue-8 -outline-offset-1 !text-n-blue-11':
|
||||
isToday(props.currentDate, day) && !isSelectedStartOrEndDate(day),
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -47,23 +47,23 @@ const onTabClick = event => {
|
||||
class="flex-shrink-0 my-0 mx-2 ltr:first:ml-0 rtl:first:mr-0 ltr:last:mr-0 rtl:last:ml-0 hover:text-n-slate-12"
|
||||
>
|
||||
<a
|
||||
class="flex items-center flex-row border-b select-none cursor-pointer text-sm relative top-[1px] transition-[border-color] duration-[150ms] ease-[cubic-bezier(0.37,0,0.63,1)]"
|
||||
class="flex items-center flex-row select-none cursor-pointer relative after:absolute after:bottom-px after:left-0 after:right-0 after:h-[2px] after:rounded-full after:transition-all after:duration-200 text-button"
|
||||
:class="[
|
||||
active
|
||||
? 'border-b border-n-brand text-n-blue-text'
|
||||
: 'border-transparent text-n-slate-11',
|
||||
isCompact ? 'py-2 text-sm' : 'text-base py-3',
|
||||
? 'text-n-blue-11 after:bg-n-brand after:opacity-100'
|
||||
: 'text-n-slate-11 after:bg-transparent after:opacity-0',
|
||||
isCompact ? 'py-2.5' : '!text-base py-3',
|
||||
]"
|
||||
@click="onTabClick"
|
||||
>
|
||||
{{ name }}
|
||||
<div
|
||||
v-if="showBadge"
|
||||
class="rounded-md h-5 flex items-center justify-center text-xxs font-semibold my-0 mx-1 px-1 py-0 min-w-[20px]"
|
||||
class="rounded-full h-5 flex items-center justify-center text-xs font-medium my-0 ltr:ml-1 rtl:mr-1 px-1.5 py-0 min-w-[20px]"
|
||||
:class="[
|
||||
active
|
||||
? 'bg-n-brand/10 dark:bg-n-brand/20 text-n-blue-text'
|
||||
: 'bg-n-alpha-black2 dark:bg-n-solid-3 text-n-slate-11',
|
||||
? 'bg-n-blue-3 text-n-blue-11'
|
||||
: 'bg-n-alpha-1 text-n-slate-10',
|
||||
]"
|
||||
>
|
||||
<span>
|
||||
|
||||
@@ -48,7 +48,7 @@ useKeyboardEvents(keyboardEvents);
|
||||
<template>
|
||||
<woot-tabs
|
||||
:index="activeTabIndex"
|
||||
class="w-full px-3 -mt-1 py-0 [&_ul]:p-0"
|
||||
class="w-full px-3 -mt-1 py-0 [&_ul]:p-0 h-10"
|
||||
@change="onTabChange"
|
||||
>
|
||||
<woot-tabs-item
|
||||
|
||||
@@ -91,7 +91,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="conversation-details-wrap flex flex-col min-w-0 w-full bg-n-background relative"
|
||||
class="conversation-details-wrap flex flex-col min-w-0 w-full bg-n-surface-1 relative"
|
||||
:class="{
|
||||
'border-l rtl:border-l-0 rtl:border-r border-n-weak': !isOnExpandedLayout,
|
||||
}"
|
||||
@@ -104,7 +104,7 @@ export default {
|
||||
<woot-tabs
|
||||
v-if="dashboardApps.length && currentChat.id"
|
||||
:index="activeIndex"
|
||||
class="-mt-px border-t border-t-n-background"
|
||||
class="h-10"
|
||||
@change="onDashboardAppTabChange"
|
||||
>
|
||||
<woot-tabs-item
|
||||
@@ -114,7 +114,6 @@ export default {
|
||||
:name="tab.name"
|
||||
:show-badge="false"
|
||||
is-compact
|
||||
class="[&_a]:pt-1"
|
||||
/>
|
||||
</woot-tabs>
|
||||
<div v-show="!activeIndex" class="flex h-full min-h-0 m-0">
|
||||
|
||||
@@ -236,9 +236,8 @@ const deleteConversation = () => {
|
||||
<div
|
||||
class="relative flex items-start flex-grow-0 flex-shrink-0 w-auto max-w-full py-0 border-t-0 border-b-0 border-l-0 border-r-0 border-transparent border-solid cursor-pointer conversation hover:bg-n-alpha-1 dark:hover:bg-n-alpha-3 group"
|
||||
:class="{
|
||||
'active animate-card-select bg-n-alpha-1 dark:bg-n-alpha-3 border-n-weak':
|
||||
isActiveChat,
|
||||
'bg-n-slate-2 dark:bg-n-slate-3': selected,
|
||||
'active animate-card-select bg-n-background border-n-weak': isActiveChat,
|
||||
'bg-n-slate-2': selected,
|
||||
'px-0': compact,
|
||||
'px-3': !compact,
|
||||
}"
|
||||
|
||||
@@ -95,7 +95,7 @@ const hasSlaPolicyId = computed(() => props.chat?.sla_policy_id);
|
||||
<template>
|
||||
<div
|
||||
ref="conversationHeader"
|
||||
class="flex flex-col gap-3 items-center justify-between flex-1 w-full min-w-0 xl:flex-row px-3 py-2 border-b bg-n-background border-n-weak h-24 xl:h-12"
|
||||
class="flex flex-col gap-3 items-center justify-between flex-1 w-full min-w-0 xl:flex-row px-3 pt-3 pb-2 h-24 xl:h-12"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-start w-full xl:w-auto max-w-full min-w-0 xl:flex-1"
|
||||
|
||||
@@ -42,7 +42,7 @@ const closeContactPanel = () => {
|
||||
<template>
|
||||
<div
|
||||
v-on-click-outside="() => closeContactPanel()"
|
||||
class="bg-n-background h-full overflow-hidden flex flex-col fixed top-0 z-40 w-full max-w-sm transition-transform duration-300 ease-in-out ltr:right-0 rtl:left-0 md:static md:w-[320px] md:min-w-[320px] ltr:border-l rtl:border-r border-n-weak 2xl:min-w-[360px] 2xl:w-[360px] shadow-lg md:shadow-none"
|
||||
class="bg-n-surface-2 h-full overflow-hidden flex flex-col fixed top-0 z-40 w-full max-w-sm transition-transform duration-300 ease-in-out ltr:right-0 rtl:left-0 md:static md:w-[320px] md:min-w-[320px] ltr:border-l rtl:border-r border-n-weak 2xl:min-w-[360px] 2xl:w-[360px] shadow-lg md:shadow-none"
|
||||
:class="[
|
||||
{
|
||||
'md:flex': activeTab === 0,
|
||||
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
) {
|
||||
return 'h-full overflow-auto w-full';
|
||||
}
|
||||
return 'flex-1 min-w-0 px-0 flex flex-col items-center justify-center h-full';
|
||||
return 'flex-1 min-w-0 px-0 flex flex-col items-center justify-center h-full bg-n-surface-1';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -502,7 +502,7 @@ export default {
|
||||
class="flex relative flex-col"
|
||||
:class="{
|
||||
'modal-mask': isPopOutReplyBox,
|
||||
'bg-n-background': !isPopOutReplyBox,
|
||||
'bg-n-surface-1': !isPopOutReplyBox,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -29,7 +29,7 @@ defineProps({
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="h-full w-full bg-n-background border border-n-weak rounded-lg p-4 flex flex-col"
|
||||
class="h-full w-full bg-n-surface-2 border border-n-weak rounded-lg p-4 flex flex-col"
|
||||
>
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<img :src="imageSrc" :alt="imageAlt" class="h-36 w-auto mx-auto" />
|
||||
|
||||
@@ -60,7 +60,7 @@ const onClickTabChange = index => {
|
||||
<div class="flex flex-col h-auto overflow-auto">
|
||||
<div class="flex flex-col px-8 pb-4 mt-1">
|
||||
<woot-tabs
|
||||
class="ltr:[&>ul]:pl-0 rtl:[&>ul]:pr-0"
|
||||
class="ltr:[&>ul]:pl-0 rtl:[&>ul]:pr-0 h-10"
|
||||
:index="selectedTabIndex"
|
||||
@change="onClickTabChange"
|
||||
>
|
||||
|
||||
@@ -94,7 +94,7 @@ onUnmounted(() => {
|
||||
class="icon"
|
||||
aria-hidden="true"
|
||||
:class="{
|
||||
'text-n-blue-text': isInputFocused,
|
||||
'text-n-blue-11': isInputFocused,
|
||||
'text-n-slate-10': !isInputFocused,
|
||||
}"
|
||||
/>
|
||||
|
||||
@@ -358,7 +358,7 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col w-full h-full bg-n-background">
|
||||
<div class="flex flex-col w-full h-full bg-n-surface-1">
|
||||
<div class="flex w-full p-4">
|
||||
<NextButton
|
||||
:label="t('GENERAL_SETTINGS.BACK')"
|
||||
|
||||
@@ -140,7 +140,9 @@ export default {
|
||||
@close-mobile-sidebar="closeMobileSidebar"
|
||||
/>
|
||||
|
||||
<main class="flex flex-1 h-full w-full min-h-0 px-0 overflow-hidden">
|
||||
<main
|
||||
class="flex flex-1 h-full w-full min-h-0 px-0 overflow-hidden bg-n-surface-1"
|
||||
>
|
||||
<UpgradePage
|
||||
v-show="showUpgradePage"
|
||||
ref="upgradePageRef"
|
||||
|
||||
@@ -16,7 +16,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col justify-between flex-1 h-full m-0 overflow-auto bg-n-background px-6"
|
||||
class="flex flex-col justify-between flex-1 h-full m-0 overflow-auto bg-n-surface-1 px-6"
|
||||
>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive v-if="keepAlive">
|
||||
|
||||
@@ -82,7 +82,7 @@ onMounted(() => performRouting());
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-center w-full bg-n-background text-n-slate-11"
|
||||
class="flex items-center justify-center w-full bg-n-surface-1 text-n-slate-11"
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ watch(
|
||||
|
||||
<template>
|
||||
<div class="flex w-full h-full min-h-0">
|
||||
<section class="flex flex-1 h-full px-0 overflow-hidden bg-n-background">
|
||||
<section class="flex flex-1 h-full px-0 overflow-hidden bg-n-surface-1">
|
||||
<router-view />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -127,7 +127,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col justify-between flex-1 h-full m-0 overflow-auto bg-n-background"
|
||||
class="flex flex-col justify-between flex-1 h-full m-0 overflow-auto bg-n-surface-1"
|
||||
>
|
||||
<ContactsDetailsLayout
|
||||
:button-label="$t('CONTACTS_LAYOUT.HEADER.SEND_MESSAGE')"
|
||||
|
||||
@@ -435,7 +435,7 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col justify-between flex-1 h-full m-0 overflow-auto bg-n-background"
|
||||
class="flex flex-col justify-between flex-1 h-full m-0 overflow-auto bg-n-surface-1"
|
||||
>
|
||||
<ContactsListLayout
|
||||
:search-value="searchValue"
|
||||
|
||||
@@ -207,7 +207,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-n-background">
|
||||
<div>
|
||||
<div class="multiselect-wrap--small">
|
||||
<ContactDetailsItem
|
||||
compact
|
||||
|
||||
@@ -155,7 +155,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative bg-n-background">
|
||||
<div class="relative">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex justify-between w-full mb-1">
|
||||
<div>
|
||||
|
||||
@@ -251,8 +251,8 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const evenClass = [
|
||||
'[&>*:nth-child(odd)]:!bg-n-background [&>*:nth-child(even)]:!bg-n-slate-2',
|
||||
'dark:[&>*:nth-child(odd)]:!bg-n-background dark:[&>*:nth-child(even)]:!bg-n-solid-1',
|
||||
'[&>*:nth-child(odd)]:!bg-n-surface-1 [&>*:nth-child(even)]:!bg-n-slate-1',
|
||||
'dark:[&>*:nth-child(odd)]:!bg-n-surface-2 dark:[&>*:nth-child(even)]:!bg-n-surface-1',
|
||||
];
|
||||
</script>
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-12 sm:gap-16 items-center justify-center py-0 px-4 w-full min-h-screen max-w-full overflow-auto bg-n-background"
|
||||
class="flex flex-col gap-12 sm:gap-16 items-center justify-center py-0 px-4 w-full min-h-screen max-w-full overflow-auto bg-n-surface-1"
|
||||
>
|
||||
<div class="flex flex-col justify-start sm:justify-center gap-6">
|
||||
<div class="flex flex-col gap-1.5 items-start sm:items-center">
|
||||
|
||||
@@ -67,7 +67,7 @@ watch(
|
||||
<div class="flex w-full h-full min-h-0">
|
||||
<section
|
||||
v-if="isHelpCenterEnabled"
|
||||
class="flex flex-1 h-full px-0 overflow-hidden bg-n-background"
|
||||
class="flex flex-1 h-full px-0 overflow-hidden bg-n-surface-1"
|
||||
>
|
||||
<router-view />
|
||||
</section>
|
||||
|
||||
@@ -69,7 +69,7 @@ onMounted(() => performRouting());
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-center w-full bg-n-background text-n-slate-11"
|
||||
class="flex items-center justify-center w-full bg-n-surface-1 text-n-slate-11"
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="items-center justify-center hidden w-full h-full text-center bg-n-background lg:flex"
|
||||
class="items-center justify-center hidden w-full h-full text-center bg-n-surface-1 lg:flex"
|
||||
>
|
||||
<div v-if="uiFlags.isFetching" class="flex justify-center my-4">
|
||||
<Spinner class="text-n-brand" />
|
||||
|
||||
@@ -222,7 +222,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex w-full h-full bg-n-solid-1">
|
||||
<section class="flex w-full h-full bg-n-surface-1">
|
||||
<div
|
||||
class="flex flex-col h-full w-full lg:min-w-[340px] lg:max-w-[340px] ltr:border-r rtl:border-l border-n-weak"
|
||||
:class="!currentConversationId ? 'flex' : 'hidden xl:flex'"
|
||||
@@ -244,7 +244,7 @@ onMounted(() => {
|
||||
class="inbox-card rounded-none hover:rounded-lg hover:bg-n-alpha-1 dark:hover:bg-n-alpha-3"
|
||||
:class="
|
||||
currentConversationId === notificationItem.primaryActor?.id
|
||||
? 'bg-n-alpha-1 dark:bg-n-alpha-3 rounded-lg active'
|
||||
? 'bg-n-alpha-1 dark:bg-n-alpha-3 !rounded-lg active'
|
||||
: ''
|
||||
"
|
||||
@mark-notification-as-read="markNotificationAsRead"
|
||||
|
||||
@@ -152,8 +152,7 @@ export default {
|
||||
<span
|
||||
class="text-xs font-medium hover:text-n-brand truncate min-w-0 dark:hover:text-n-brand"
|
||||
:class="{
|
||||
'text-n-blue-text dark:text-n-blue-text':
|
||||
activeSort === option.key,
|
||||
'text-n-blue-11 dark:text-n-blue-11': activeSort === option.key,
|
||||
'text-n-slate-11': activeSort !== option.key,
|
||||
}"
|
||||
>
|
||||
@@ -161,7 +160,7 @@ export default {
|
||||
</span>
|
||||
<span
|
||||
v-if="activeSort === option.key"
|
||||
class="i-lucide-check size-2.5 flex-shrink-0 text-n-blue-text"
|
||||
class="i-lucide-check size-2.5 flex-shrink-0 text-n-blue-11"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -109,7 +109,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between w-full gap-2 border-b px-3 h-12 rtl:border-r border-n-weak flex-shrink-0"
|
||||
class="flex items-center justify-between w-full gap-2 border-b px-3 h-12 rtl:border-r border-n-weak flex-shrink-0 bg-n-surface-1"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<BackButton
|
||||
|
||||
@@ -10,7 +10,7 @@ defineProps({
|
||||
<template>
|
||||
<div
|
||||
role="button"
|
||||
class="flex items-center w-full h-8 px-2 py-1 rounded-md cursor-pointer hover:text-n-blue-text min-w-0"
|
||||
class="flex items-center w-full h-8 px-2 py-1 rounded-md cursor-pointer hover:text-n-blue-11 min-w-0"
|
||||
>
|
||||
<span class="text-xs font-medium truncate text-n-slate-12">
|
||||
{{ label }}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
export const NOTIFICATION_TYPES_MAPPING = {
|
||||
CONVERSATION_MENTION: ['i-lucide-at-sign', 'text-n-blue-text'],
|
||||
CONVERSATION_ASSIGNMENT: ['i-lucide-chevrons-right', 'text-n-blue-text'],
|
||||
CONVERSATION_CREATION: ['i-lucide-mail-plus', 'text-n-blue-text'],
|
||||
CONVERSATION_MENTION: ['i-lucide-at-sign', 'text-n-blue-11'],
|
||||
CONVERSATION_ASSIGNMENT: ['i-lucide-chevrons-right', 'text-n-blue-11'],
|
||||
CONVERSATION_CREATION: ['i-lucide-mail-plus', 'text-n-blue-11'],
|
||||
PARTICIPATING_CONVERSATION_NEW_MESSAGE: [
|
||||
'i-lucide-message-square-plus',
|
||||
'text-n-blue-text',
|
||||
'text-n-blue-11',
|
||||
],
|
||||
ASSIGNED_CONVERSATION_NEW_MESSAGE: [
|
||||
'i-lucide-message-square-plus',
|
||||
'text-n-blue-text',
|
||||
'text-n-blue-11',
|
||||
],
|
||||
SLA_MISSED_FIRST_RESPONSE: ['i-lucide-heart-crack', 'text-n-ruby-11'],
|
||||
SLA_MISSED_NEXT_RESPONSE: ['i-lucide-heart-crack', 'text-n-ruby-11'],
|
||||
|
||||
@@ -41,7 +41,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex justify-between items-center h-20 min-h-[3.5rem] px-4 py-2 bg-n-background"
|
||||
class="flex justify-between items-center h-20 min-h-[3.5rem] px-4 py-2 bg-n-surface-1"
|
||||
>
|
||||
<h1 class="flex items-center mb-0 text-2xl text-n-slate-12">
|
||||
<BackButton
|
||||
|
||||
@@ -9,7 +9,7 @@ defineProps({
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col w-full h-full m-0 p-6 sm:py-8 lg:px-16 overflow-auto bg-n-background font-inter"
|
||||
class="flex flex-col w-full h-full m-0 p-6 sm:py-8 lg:px-16 overflow-auto bg-n-surface-1 font-inter"
|
||||
>
|
||||
<div class="flex items-start w-full max-w-6xl mx-auto">
|
||||
<router-view v-slot="{ Component }">
|
||||
|
||||
@@ -19,7 +19,7 @@ const showSettingsHeader = computed(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-1 flex-col m-0 bg-n-background overflow-auto">
|
||||
<div class="flex flex-1 flex-col m-0 bg-n-surface-1 overflow-auto">
|
||||
<div
|
||||
class="mx-auto w-full flex flex-col flex-1"
|
||||
:class="{ 'max-w-6xl': !fullWidth }"
|
||||
|
||||
@@ -85,12 +85,12 @@ const openInNewTab = url => {
|
||||
:href="helpURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="items-center hidden gap-1 text-sm font-medium sm:inline-flex w-fit text-n-blue-text hover:underline"
|
||||
class="items-center hidden gap-1 text-sm font-medium sm:inline-flex w-fit text-n-blue-11 hover:underline"
|
||||
>
|
||||
{{ linkText }}
|
||||
<Icon
|
||||
icon="i-lucide-chevron-right"
|
||||
class="flex-shrink-0 text-n-blue-text size-4"
|
||||
class="flex-shrink-0 text-n-blue-11 size-4"
|
||||
/>
|
||||
</a>
|
||||
</CustomBrandPolicyWrapper>
|
||||
|
||||
@@ -509,7 +509,7 @@ export default {
|
||||
:header-title="inboxName"
|
||||
>
|
||||
<woot-tabs
|
||||
class="[&_ul]:p-0"
|
||||
class="[&_ul]:p-0 top-px relative"
|
||||
:index="selectedTabIndex"
|
||||
:border="false"
|
||||
@change="onTabChange"
|
||||
|
||||
@@ -210,7 +210,7 @@ export default {
|
||||
<div>
|
||||
<span
|
||||
v-if="isDayEnabled && !hasError"
|
||||
class="label bg-n-brand/10 dark:bg-n-brand/30 text-n-blue-text text-xs inline-block px-2 py-1 rounded-lg cursor-default whitespace-nowrap"
|
||||
class="label bg-n-brand/10 dark:bg-n-brand/30 text-n-blue-11 text-xs inline-block px-2 py-1 rounded-lg cursor-default whitespace-nowrap"
|
||||
>
|
||||
{{ totalHours }}
|
||||
</span>
|
||||
|
||||
@@ -48,7 +48,7 @@ export default {
|
||||
<div class="macro__node">
|
||||
<div>
|
||||
<span
|
||||
class="bg-n-solid-blue text-n-blue-text py-1 px-1.5 leading-none text-sm rounded-md"
|
||||
class="bg-n-solid-blue text-n-blue-11 py-1 px-1.5 leading-none text-sm rounded-md"
|
||||
>
|
||||
{{ $t('MACROS.EDITOR.START_FLOW') }}
|
||||
</span>
|
||||
@@ -102,7 +102,7 @@ export default {
|
||||
<div class="macro__node">
|
||||
<div>
|
||||
<span
|
||||
class="bg-n-solid-blue text-n-blue-text py-1 px-1.5 leading-none text-sm rounded-md"
|
||||
class="bg-n-solid-blue text-n-blue-11 py-1 px-1.5 leading-none text-sm rounded-md"
|
||||
>
|
||||
{{ $t('MACROS.EDITOR.END_FLOW') }}
|
||||
</span>
|
||||
|
||||
@@ -9,7 +9,7 @@ defineProps({
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col justify-between flex-1 h-full m-0 overflow-auto bg-n-background"
|
||||
class="flex flex-col justify-between flex-1 h-full m-0 overflow-auto bg-n-surface-1"
|
||||
>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive v-if="keepAlive">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="reports--wrapper overflow-auto bg-n-background w-full px-6">
|
||||
<div class="reports--wrapper overflow-auto bg-n-surface-1 w-full px-6">
|
||||
<div class="max-w-[60rem] mx-auto pb-12">
|
||||
<router-view />
|
||||
</div>
|
||||
|
||||
Binary file not shown.
BIN
app/javascript/shared/assets/fonts/Inter/InterVariable.woff2
Normal file
BIN
app/javascript/shared/assets/fonts/Inter/InterVariable.woff2
Normal file
Binary file not shown.
@@ -1,55 +1,17 @@
|
||||
// Inter Variable Font - Supports any weight from 100-900
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('shared/assets/fonts/Inter/Inter-Thin.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('shared/assets/fonts/Inter/Inter-Light.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('shared/assets/fonts/Inter/Inter-Regular.woff2') format('woff2');
|
||||
src: url('shared/assets/fonts/Inter/InterVariable.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('shared/assets/fonts/Inter/Inter-Italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('shared/assets/fonts/Inter/Inter-Medium.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('shared/assets/fonts/Inter/Inter-SemiBold.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('shared/assets/fonts/Inter/Inter-Bold.woff2') format('woff2');
|
||||
src: url('shared/assets/fonts/Inter/InterVariable-Italic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ export default {
|
||||
<div
|
||||
v-if="shouldShowHeaderMessage"
|
||||
v-dompurify-html="formatMessage(headerMessage, false)"
|
||||
class="mb-4 text-base leading-5 text-n-slate-12 [&>p>.link]:text-n-blue-text [&>p>.link]:hover:underline"
|
||||
class="mb-4 text-base leading-5 text-n-slate-12 [&>p>.link]:text-n-blue-11 [&>p>.link]:hover:underline"
|
||||
/>
|
||||
<!-- Why do the v-bind shenanigan? Because Formkit API is really bad.
|
||||
If we just pass the options as is even with null or undefined or false,
|
||||
|
||||
@@ -43,6 +43,12 @@ const tailwindConfig = {
|
||||
inter: ['Inter', ...defaultSansFonts],
|
||||
interDisplay: ['InterDisplay', ...defaultSansFonts],
|
||||
},
|
||||
fontWeight: {
|
||||
420: '420',
|
||||
440: '440',
|
||||
460: '460',
|
||||
520: '520',
|
||||
},
|
||||
typography: {
|
||||
bubble: {
|
||||
css: {
|
||||
@@ -187,6 +193,7 @@ const tailwindConfig = {
|
||||
lg: '1024px',
|
||||
xl: '1280px',
|
||||
'2xl': '1536px',
|
||||
'3xl': '1900px',
|
||||
},
|
||||
fontSize: {
|
||||
...defaultTheme.fontSize,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user