feat: update colors for v4 (#10660)

Porting changes from https://github.com/chatwoot/chatwoot/pull/10552

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
Co-authored-by: Sojan <sojan@pepalo.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
Shivam Mishra 2025-01-15 17:13:03 +05:30 committed by GitHub
parent a4c0c76fa5
commit 7fd8b4d03a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 660 additions and 795 deletions

View File

@ -1,7 +1,7 @@
.dropdown-pane { .dropdown-pane {
@apply border rounded-lg hidden relative invisible shadow-lg border-slate-25 dark:border-slate-700 box-content p-2 w-fit z-[9999]; @apply border rounded-lg hidden relative invisible shadow-lg border-n-strong dark:border-n-strong box-content p-2 w-fit z-[9999];
&.dropdown-pane--open { &.dropdown-pane--open {
@apply bg-white absolute dark:bg-slate-800 block visible; @apply bg-n-alpha-3 backdrop-blur-[100px] absolute block visible;
} }
} }

View File

@ -31,7 +31,7 @@ button {
} }
.button { .button {
@apply items-center bg-woot-500 dark:bg-woot-500 px-2.5 text-white dark:text-white inline-flex h-10 mb-0 gap-2 font-medium; @apply items-center bg-n-brand px-2.5 text-white dark:text-white inline-flex h-10 mb-0 gap-2 font-medium;
.button__content { .button__content {
@apply w-full whitespace-nowrap overflow-hidden text-ellipsis; @apply w-full whitespace-nowrap overflow-hidden text-ellipsis;
@ -42,8 +42,8 @@ button {
} }
} }
&:hover { &:hover:not(.secondary):not(.success):not(.alert):not(.warning):not(.clear) {
@apply bg-woot-600 dark:bg-woot-600; @apply bg-n-brand/80 dark:bg-n-brand/80;
} }
&:disabled, &:disabled,
@ -52,23 +52,23 @@ button {
} }
&.success { &.success {
@apply bg-[#44ce4b] dark:bg-[#44ce4b] text-white dark:text-white; @apply bg-n-teal-9 text-white dark:text-white;
} }
&.secondary { &.secondary {
@apply bg-slate-700 dark:bg-slate-600 text-white dark:text-white; @apply bg-n-solid-3 text-white dark:text-white;
} }
&.primary { &.primary {
@apply bg-woot-500 dark:bg-woot-500 text-white dark:text-white; @apply bg-n-brand text-white dark:text-white;
} }
&.clear { &.clear {
@apply text-woot-500 dark:text-woot-500 bg-transparent dark:bg-transparent; @apply text-n-blue-text dark:text-n-blue-text bg-transparent dark:bg-transparent;
} }
&.alert { &.alert {
@apply bg-red-500 dark:bg-red-500 text-white dark:text-white; @apply bg-n-ruby-9 text-white dark:text-white;
&.clear { &.clear {
@apply bg-transparent dark:bg-transparent; @apply bg-transparent dark:bg-transparent;
@ -76,7 +76,7 @@ button {
} }
&.warning { &.warning {
@apply bg-[#ffc532] dark:bg-[#ffc532] text-white dark:text-white; @apply bg-n-amber-9 text-white dark:text-white;
&.clear { &.clear {
@apply bg-transparent dark:bg-transparent; @apply bg-transparent dark:bg-transparent;
@ -115,114 +115,74 @@ button {
} }
&.hollow { &.hollow {
@apply border border-woot-500 bg-transparent dark:bg-transparent dark:border-woot-500 text-woot-500 dark:text-woot-500 hover:bg-woot-50 dark:hover:bg-woot-900; @apply border border-n-brand/40 bg-transparent text-n-blue-text hover:bg-n-brand/20;
&.secondary { &.secondary {
@apply text-slate-700 border-slate-100 dark:border-slate-800 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700; @apply text-n-slate-12 border-n-slate-5 hover:bg-n-slate-5;
} }
&.success { &.success {
@apply text-green-700 dark:text-green-400 border-green-100 dark:border-green-600 hover:bg-green-50 dark:hover:bg-green-800; @apply text-n-teal-9 border-n-teal-8 hover:bg-n-teal-5;
} }
&.alert { &.alert {
@apply text-red-700 dark:text-red-400 border-red-100 dark:border-red-600 hover:bg-red-50 dark:hover:bg-red-800; @apply text-n-ruby-9 border-n-ruby-8 hover:bg-n-ruby-5;
} }
&.warning { &.warning {
@apply text-yellow-600 dark:text-yellow-600 border-yellow-600 dark:border-yellow-700 hover:bg-yellow-50 dark:hover:bg-yellow-800; @apply text-n-amber-9 border-n-amber-8 hover:bg-n-amber-5;
}
&:hover {
@apply bg-woot-75 dark:bg-woot-800 border-slate-100 dark:border-woot-600 dark:text-woot-400;
&.secondary {
@apply border-slate-100 dark:border-slate-700 text-slate-800 dark:text-slate-100;
}
&.success {
@apply border-slate-100 dark:border-slate-700 text-green-800 dark:text-green-100;
}
&.alert {
@apply border-slate-100 dark:border-slate-700 text-red-700 dark:text-red-100;
}
&.warning {
@apply border-slate-100 dark:border-slate-700 text-yellow-700 dark:text-yellow-500;
}
} }
} }
// Smooth style // Smooth style
&.smooth { &.smooth {
@apply bg-woot-50 dark:bg-woot-800 text-woot-700 dark:text-woot-100 hover:text-woot-700 dark:hover:text-woot-700 hover:bg-woot-100 dark:hover:bg-woot-900; @apply bg-n-brand/10 dark:bg-n-brand/30 text-n-blue-text hover:bg-n-brand/20 dark:hover:bg-n-brand/40;
&.secondary { &.secondary {
@apply bg-slate-50 dark:bg-slate-700 text-slate-700 dark:text-slate-100 hover:bg-slate-100 dark:hover:bg-slate-800; @apply bg-n-slate-4 text-n-slate-11 hover:text-n-slate-11 hover:bg-n-slate-5;
} }
&.success { &.success {
@apply bg-green-50 dark:bg-green-700 text-green-700 dark:text-green-100 hover:bg-green-100 dark:hover:bg-green-800 hover:text-green-800 dark:hover:text-green-100; @apply bg-n-teal-4 text-n-teal-11 hover:text-n-teal-11 hover:bg-n-teal-5;
} }
&.alert { &.alert {
@apply bg-red-50 dark:bg-red-700 dark:bg-opacity-50 text-red-700 dark:text-red-100 hover:bg-red-100 dark:hover:bg-red-800 dark:hover:bg-opacity-30; @apply bg-n-ruby-4 text-n-ruby-11 hover:text-n-ruby-11 hover:bg-n-ruby-5;
} }
&.warning { &.warning {
@apply bg-yellow-100 dark:bg-yellow-100 text-yellow-700 dark:text-yellow-700 hover:bg-yellow-200 dark:hover:bg-yellow-200; @apply bg-n-amber-4 text-n-amber-11 hover:text-n-amber-11 hover:bg-n-amber-5;
} }
} }
&.clear { &.clear {
@apply text-woot-500 dark:text-woot-500; @apply text-n-blue-text hover:bg-n-brand/10 dark:hover:bg-n-brand/30;
&.secondary { &.secondary {
@apply text-slate-700 dark:text-slate-100; @apply text-n-slate-12 hover:bg-n-slate-4;
} }
&.success { &.success {
@apply text-green-700 dark:text-green-100; @apply text-n-teal-10 hover:bg-n-teal-4;
} }
&.alert { &.alert {
@apply text-red-700 dark:text-red-100; @apply text-n-ruby-11 hover:bg-n-ruby-4;
} }
&.warning { &.warning {
@apply text-yellow-700 dark:text-yellow-600; @apply text-n-amber-11 hover:bg-n-amber-4;
}
&:hover {
@apply hover:bg-woot-50 dark:hover:bg-woot-900/50 hover:text-woot-500 dark:hover:text-woot-100;
&.secondary {
@apply hover:bg-slate-50 dark:hover:bg-slate-700 hover:text-slate-800 dark:hover:text-slate-100;
}
&.success {
@apply hover:bg-green-50 dark:hover:bg-green-800 hover:text-green-800 dark:hover:text-green-100;
}
&.alert {
@apply hover:bg-red-50 dark:hover:bg-red-800 hover:text-red-700 dark:hover:text-red-100;
}
&.warning {
@apply hover:bg-yellow-100 dark:hover:bg-yellow-800 hover:text-yellow-700 dark:hover:text-yellow-600;
}
} }
&:active { &:active {
&.secondary { &.secondary {
@apply active:bg-slate-100 dark:active:bg-slate-900; @apply active:bg-n-slate-3 dark:active:bg-n-slate-7;
} }
} }
&:focus { &:focus {
&.secondary { &.secondary {
@apply focus:bg-slate-50 dark:focus:bg-slate-700; @apply focus:bg-n-slate-4 dark:focus:bg-n-slate-6;
} }
} }
} }

View File

@ -3,7 +3,7 @@
} }
.tabs--container--with-border { .tabs--container--with-border {
@apply border-b border-slate-50 dark:border-slate-800/50; @apply border-b border-n-weak;
} }
.tabs--container--compact.tab--chat-type { .tabs--container--compact.tab--chat-type {
@ -42,7 +42,7 @@
@apply flex-shrink-0 my-0 mx-2; @apply flex-shrink-0 my-0 mx-2;
.badge { .badge {
@apply bg-slate-50 dark:bg-slate-800 rounded-md text-slate-600 dark:text-slate-100 h-5 flex items-center justify-center text-xxs font-semibold my-0 mx-1 px-1 py-0; @apply bg-n-alpha-black2 dark:bg-n-solid-3 rounded-md text-n-slate-11 h-5 flex items-center justify-center text-xxs font-semibold my-0 mx-1 px-1 py-0;
} }
&:first-child { &:first-child {
@ -56,22 +56,22 @@
&:hover, &:hover,
&:focus { &:focus {
a { a {
@apply text-slate-800 dark:text-slate-100; @apply text-n-slate-12;
} }
} }
a { a {
@apply flex items-center flex-row border-b py-2.5 select-none cursor-pointer border-transparent text-slate-500 dark:text-slate-200 text-sm top-[1px] relative; @apply flex items-center flex-row border-b py-2.5 select-none cursor-pointer border-transparent text-n-slate-11 text-sm top-[1px] relative;
transition: border-color 0.15s $swift-ease-out-function; transition: border-color 0.15s $swift-ease-out-function;
} }
&.is-active { &.is-active {
a { a {
@apply border-b border-woot-500 text-woot-500 dark:text-woot-500; @apply border-b border-n-brand text-n-blue-text;
} }
.badge { .badge {
@apply bg-woot-50 dark:bg-woot-500 text-woot-500 dark:text-woot-50 dark:bg-opacity-40; @apply bg-n-brand/10 dark:bg-n-brand/20 text-n-blue-text;
} }
} }
} }

View File

@ -189,7 +189,10 @@ useKeyboardEvents(keyboardEvents);
<template> <template>
<div <div
v-on-click-outside="() => (showComposeNewConversation = false)" v-on-click-outside="() => (showComposeNewConversation = false)"
class="relative z-40" class="relative"
:class="{
'z-40': showComposeNewConversation,
}"
> >
<slot <slot
name="trigger" name="trigger"

View File

@ -174,7 +174,7 @@ watch(
</script> </script>
<template> <template>
<span class="relative inline-flex group/avatar" :style="containerStyles"> <span class="relative inline-flex group/avatar z-0" :style="containerStyles">
<!-- Status Badge --> <!-- Status Badge -->
<slot name="badge" :size="size"> <slot name="badge" :size="size">
<div <div

View File

@ -6,6 +6,7 @@ import Icon from 'dashboard/components-next/icon/Icon.vue';
import { import {
VARIANT_OPTIONS, VARIANT_OPTIONS,
COLOR_OPTIONS, COLOR_OPTIONS,
JUSTIFY_OPTIONS,
SIZE_OPTIONS, SIZE_OPTIONS,
EXCLUDED_ATTRS, EXCLUDED_ATTRS,
} from './constants.js'; } from './constants.js';
@ -27,6 +28,11 @@ const props = defineProps({
default: null, default: null,
validator: value => SIZE_OPTIONS.includes(value) || value === null, validator: value => SIZE_OPTIONS.includes(value) || value === null,
}, },
justify: {
type: String,
default: null,
validator: value => JUSTIFY_OPTIONS.includes(value) || value === null,
},
icon: { type: [String, Object, Function], default: '' }, icon: { type: [String, Object, Function], default: '' },
trailingIcon: { type: Boolean, default: false }, trailingIcon: { type: Boolean, default: false },
isLoading: { type: Boolean, default: false }, isLoading: { type: Boolean, default: false },
@ -81,6 +87,15 @@ const computedSize = computed(() => {
return 'md'; return 'md';
}); });
const computedJustify = computed(() => {
if (props.justify) return props.justify;
if (attrs.start || attrs.start === '') return 'start';
if (attrs.center || attrs.center === '') return 'center';
if (attrs.end || attrs.end === '') return 'end';
return 'center';
});
const STYLE_CONFIG = { const STYLE_CONFIG = {
colors: { colors: {
blue: { blue: {
@ -151,7 +166,12 @@ const STYLE_CONFIG = {
md: 'text-sm font-medium', md: 'text-sm font-medium',
lg: 'text-base', 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', justify: {
start: 'justify-start',
center: 'justify-center',
end: 'justify-end',
},
base: 'inline-flex items-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 variantClasses = computed(() => { const variantClasses = computed(() => {
@ -197,6 +217,7 @@ const linkButtonClasses = computed(() => {
[STYLE_CONFIG.base]: true, [STYLE_CONFIG.base]: true,
[isLink ? linkButtonClasses : buttonClasses]: true, [isLink ? linkButtonClasses : buttonClasses]: true,
[STYLE_CONFIG.fontSize[computedSize]]: true, [STYLE_CONFIG.fontSize[computedSize]]: true,
[STYLE_CONFIG.justify[computedJustify]]: true,
'flex-row-reverse': trailingIcon && !isIconOnly, 'flex-row-reverse': trailingIcon && !isIconOnly,
}" }"
> >

View File

@ -1,6 +1,7 @@
export const VARIANT_OPTIONS = ['solid', 'outline', 'faded', 'link', 'ghost']; export const VARIANT_OPTIONS = ['solid', 'outline', 'faded', 'link', 'ghost'];
export const COLOR_OPTIONS = ['blue', 'ruby', 'amber', 'slate', 'teal']; export const COLOR_OPTIONS = ['blue', 'ruby', 'amber', 'slate', 'teal'];
export const SIZE_OPTIONS = ['xs', 'sm', 'md', 'lg']; export const SIZE_OPTIONS = ['xs', 'sm', 'md', 'lg'];
export const JUSTIFY_OPTIONS = ['start', 'center', 'end'];
export const EXCLUDED_ATTRS = [ export const EXCLUDED_ATTRS = [
'variant', 'variant',
@ -12,4 +13,5 @@ export const EXCLUDED_ATTRS = [
...VARIANT_OPTIONS, ...VARIANT_OPTIONS,
...COLOR_OPTIONS, ...COLOR_OPTIONS,
...SIZE_OPTIONS, ...SIZE_OPTIONS,
...JUSTIFY_OPTIONS,
]; ];

View File

@ -150,6 +150,6 @@ defineExpose({ open, close });
<style scoped> <style scoped>
dialog::backdrop { dialog::backdrop {
@apply dark:bg-n-alpha-white bg-n-alpha-black2; @apply bg-n-alpha-black1 backdrop-blur-[4px];
} }
</style> </style>

View File

@ -431,12 +431,12 @@ provideMessageContext({
<div <div
v-if="shouldRenderMessage" v-if="shouldRenderMessage"
:id="`message${props.id}`" :id="`message${props.id}`"
class="flex w-full message-bubble-container" class="flex w-full message-bubble-container mb-2"
:data-message-id="props.id" :data-message-id="props.id"
:class="[ :class="[
flexOrientationClass, flexOrientationClass,
shouldGroupWithNext ? 'group-with-next mb-2' : 'mb-4',
{ {
'group-with-next': shouldGroupWithNext,
'bg-n-alpha-1': showBackgroundHighlight, 'bg-n-alpha-1': showBackgroundHighlight,
}, },
]" ]"
@ -449,7 +449,7 @@ provideMessageContext({
:class="[ :class="[
gridClass, gridClass,
{ {
'gap-y-2': !shouldGroupWithNext, 'gap-y-2': contentAttributes.externalError,
'w-full': variant === MESSAGE_VARIANTS.EMAIL, 'w-full': variant === MESSAGE_VARIANTS.EMAIL,
}, },
]" ]"

View File

@ -28,10 +28,7 @@ const senderName = computed(() => {
</script> </script>
<template> <template>
<BaseBubble <BaseBubble class="overflow-hidden p-3" data-bubble-name="attachment">
class="overflow-hidden p-3 !bg-n-solid-2 shadow-[0px_0px_12px_0px_rgba(0,0,0,0.05)]"
data-bubble-name="attachment"
>
<div class="grid gap-4 min-w-64"> <div class="grid gap-4 min-w-64">
<div class="grid gap-3 z-20"> <div class="grid gap-3 z-20">
<div <div
@ -57,7 +54,7 @@ const senderName = computed(() => {
</slot> </slot>
</div> </div>
</div> </div>
<div v-if="action"> <div v-if="action" class="mb-2">
<a <a
v-if="action.href" v-if="action.href"
:href="action.href" :href="action.href"

View File

@ -21,7 +21,7 @@ const showQuotedMessage = ref(false);
const contentContainer = useTemplateRef('contentContainer'); const contentContainer = useTemplateRef('contentContainer');
onMounted(() => { onMounted(() => {
isExpandable.value = contentContainer.value.scrollHeight > 400; isExpandable.value = contentContainer.value?.scrollHeight > 400;
}); });
const isOutgoing = computed(() => { const isOutgoing = computed(() => {

View File

@ -26,7 +26,7 @@ const currentTime = ref(0);
const duration = ref(0); const duration = ref(0);
const onLoadedMetadata = () => { const onLoadedMetadata = () => {
duration.value = audioPlayer.value.duration; duration.value = audioPlayer.value?.duration;
}; };
const formatTime = time => { const formatTime = time => {

View File

@ -90,7 +90,8 @@ const allowedMenuItems = computed(() => {
<template> <template>
<DropdownContainer <DropdownContainer
class="relative z-20 w-full min-w-0" class="relative w-full min-w-0"
:class="{ 'z-20': isOpen }"
@close="emit('close')" @close="emit('close')"
> >
<template #trigger="{ toggle, isOpen }"> <template #trigger="{ toggle, isOpen }">

View File

@ -33,22 +33,21 @@ const onToggle = () => {
</script> </script>
<template> <template>
<div class="-mt-px text-sm"> <div class="text-sm">
<button <button
class="flex items-center select-none w-full rounded-none bg-slate-50 dark:bg-slate-800 border border-l-0 border-r-0 border-solid m-0 border-slate-100 dark:border-slate-700/50 cursor-grab justify-between py-2 px-4 drag-handle" class="flex items-center select-none w-full rounded-lg bg-n-slate-2 border border-n-weak m-0 cursor-grab justify-between py-2 px-4 drag-handle"
:class="{ 'rounded-bl-none rounded-br-none': isOpen }"
@click.stop="onToggle" @click.stop="onToggle"
> >
<div class="flex justify-between mb-0.5"> <div class="flex justify-between">
<EmojiOrIcon class="inline-block w-5" :icon="icon" :emoji="emoji" /> <EmojiOrIcon class="inline-block w-5" :icon="icon" :emoji="emoji" />
<h5 <h5 class="text-n-slate-12 text-sm mb-0 py-0 pr-2 pl-0">
class="text-slate-800 text-sm dark:text-slate-100 mb-0 py-0 pr-2 pl-0"
>
{{ title }} {{ title }}
</h5> </h5>
</div> </div>
<div class="flex flex-row"> <div class="flex flex-row">
<slot name="button" /> <slot name="button" />
<div class="flex justify-end w-3 text-woot-500"> <div class="flex justify-end w-3 text-n-blue-text cursor-pointer">
<fluent-icon v-if="isOpen" size="24" icon="subtract" type="solid" /> <fluent-icon v-if="isOpen" size="24" icon="subtract" type="solid" />
<fluent-icon v-else size="24" icon="add" type="solid" /> <fluent-icon v-else size="24" icon="add" type="solid" />
</div> </div>
@ -56,8 +55,8 @@ const onToggle = () => {
</button> </button>
<div <div
v-if="isOpen" v-if="isOpen"
class="bg-white dark:bg-slate-900" class="bg-n-background border border-n-weak dark:border-n-slate-2 border-t-0 rounded-br-lg rounded-bl-lg overflow-hidden"
:class="compact ? 'p-0' : 'p-4'" :class="compact ? 'p-0' : 'px-2 py-4'"
> >
<slot /> <slot />
</div> </div>

View File

@ -782,7 +782,7 @@ watch(conversationFilters, (newVal, oldVal) => {
<template> <template>
<div <div
class="flex flex-col flex-shrink-0 conversations-list-wrap" class="flex flex-col flex-shrink-0 bg-n-solid-1 conversations-list-wrap"
:class="[ :class="[
{ hidden: !showConversationList }, { hidden: !showConversationList },
isOnExpandedLayout ? 'basis-full' : 'w-[360px] 2xl:w-[420px]', isOnExpandedLayout ? 'basis-full' : 'w-[360px] 2xl:w-[420px]',

View File

@ -80,10 +80,11 @@ const toggleConversationLayout = () => {
<template> <template>
<div <div
class="flex items-center justify-between gap-2 px-4 pb-0 mb-2" class="flex items-center justify-between gap-2 px-4"
:class="{ :class="{
'pb-3 border-b border-n-strong': hasAppliedFiltersOrActiveFolders, 'pb-3 border-b border-n-strong': hasAppliedFiltersOrActiveFolders,
'pt-2.5': showV4View, 'pt-3 pb-2': showV4View,
'mb-2 pb-0': !showV4View,
}" }"
> >
<div class="flex items-center justify-center min-w-0"> <div class="flex items-center justify-center min-w-0">

View File

@ -1,60 +1,61 @@
<script> <script setup>
import { computed } from 'vue';
import 'highlight.js/styles/default.css'; import 'highlight.js/styles/default.css';
import 'highlight.js/lib/common'; import 'highlight.js/lib/common';
import NextButton from 'dashboard/components-next/button/Button.vue';
import { copyTextToClipboard } from 'shared/helpers/clipboard'; import { copyTextToClipboard } from 'shared/helpers/clipboard';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
export default { const props = defineProps({
props: { script: {
script: { type: String,
type: String, default: '',
default: '',
},
lang: {
type: String,
default: 'javascript',
},
enableCodePen: {
type: Boolean,
default: false,
},
codepenTitle: {
type: String,
default: 'Chatwoot Codepen',
},
}, },
computed: { lang: {
codepenScriptValue() { type: String,
const lang = this.lang === 'javascript' ? 'js' : this.lang; default: 'javascript',
return JSON.stringify({ },
title: this.codepenTitle, enableCodePen: {
private: true, type: Boolean,
[lang]: this.scrubbedScript, default: false,
}); },
}, codepenTitle: {
scrubbedScript() { type: String,
// remove trailing and leading extra lines and not spaces default: 'Chatwoot Codepen',
const scrubbed = this.script.replace(/^\s*[\r\n]/gm, ''); },
const lines = scrubbed.split('\n'); });
// remove extra indentations const { t } = useI18n();
const minIndent = lines.reduce((min, line) => {
if (line.trim().length === 0) return min;
const indent = line.match(/^\s*/)[0].length;
return Math.min(min, indent);
}, Infinity);
return lines.map(line => line.slice(minIndent)).join('\n'); const scrubbedScript = computed(() => {
}, // remove trailing and leading extra lines and not spaces
}, const scrubbed = props.script.replace(/^\s*[\r\n]/gm, '');
methods: { const lines = scrubbed.split('\n');
async onCopy(e) {
e.preventDefault(); // remove extra indentations
await copyTextToClipboard(this.scrubbedScript); const minIndent = lines.reduce((min, line) => {
useAlert(this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL')); if (line.trim().length === 0) return min;
}, const indent = line.match(/^\s*/)[0].length;
}, return Math.min(min, indent);
}, Infinity);
return lines.map(line => line.slice(minIndent)).join('\n');
});
const codepenScriptValue = computed(() => {
const lang = props.lang === 'javascript' ? 'js' : props.lang;
return JSON.stringify({
title: props.codepenTitle,
private: true,
[lang]: scrubbedScript.value,
});
});
const onCopy = async e => {
e.preventDefault();
await copyTextToClipboard(scrubbedScript.value);
useAlert(t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
}; };
</script> </script>
@ -69,13 +70,21 @@ export default {
target="_blank" target="_blank"
> >
<input type="hidden" name="data" :value="codepenScriptValue" /> <input type="hidden" name="data" :value="codepenScriptValue" />
<button type="submit" class="button secondary tiny"> <NextButton
{{ $t('COMPONENTS.CODE.CODEPEN') }} slate
</button> xs
type="submit"
faded
:label="t('COMPONENTS.CODE.CODEPEN')"
/>
</form> </form>
<button type="button" class="button secondary tiny" @click="onCopy"> <NextButton
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }} slate
</button> xs
faded
:label="t('COMPONENTS.CODE.BUTTON_TEXT')"
@click="onCopy"
/>
</div> </div>
<highlightjs v-if="script" :language="lang" :code="scrubbedScript" /> <highlightjs v-if="script" :language="lang" :code="scrubbedScript" />
</div> </div>

View File

@ -209,9 +209,7 @@ export default {
<span <span
class="w-full inline-flex gap-1.5 items-start font-medium whitespace-nowrap text-sm mb-0" class="w-full inline-flex gap-1.5 items-start font-medium whitespace-nowrap text-sm mb-0"
:class=" :class="
v$.editedValue.$error v$.editedValue.$error ? 'text-n-ruby-11' : 'text-n-slate-12'
? 'text-red-400 dark:text-red-500'
: 'text-slate-800 dark:text-slate-100'
" "
> >
{{ label }} {{ label }}
@ -228,7 +226,7 @@ export default {
size="medium" size="medium"
color-scheme="secondary" color-scheme="secondary"
icon="delete" icon="delete"
class-names="flex justify-end w-4" class-names="flex justify-end !w-fit"
@click="onDelete" @click="onDelete"
/> />
</div> </div>
@ -251,14 +249,14 @@ export default {
<woot-button <woot-button
size="small" size="small"
icon="checkmark" icon="checkmark"
class="rounded-l-none rtl:rounded-r-none" class="ltr:rounded-l-none rtl:rounded-r-none"
@click="onUpdate" @click="onUpdate"
/> />
</div> </div>
</div> </div>
<span <span
v-if="shouldShowErrorMessage" v-if="shouldShowErrorMessage"
class="block w-full -mt-px text-sm font-normal text-red-400 dark:text-red-500" class="block w-full -mt-px text-sm font-normal text-n-ruby-11"
> >
{{ errorMessage }} {{ errorMessage }}
</span> </span>
@ -273,13 +271,13 @@ export default {
:href="hrefURL" :href="hrefURL"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="group-hover:bg-slate-50 group-hover:dark:bg-slate-700 inline-block rounded-sm mb-0 break-all py-0.5 px-1" class="group-hover:bg-n-slate-3 group-hover:dark:bg-n-solid-3 inline-block rounded-sm mb-0 break-all py-0.5 px-1"
> >
{{ urlValue }} {{ urlValue }}
</a> </a>
<p <p
v-else v-else
class="group-hover:bg-slate-50 group-hover:dark:bg-slate-700 inline-block rounded-sm mb-0 break-all py-0.5 px-1" class="group-hover:bg-n-slate-3 group-hover:dark:bg-n-solid-3 inline-block rounded-sm mb-0 break-all py-0.5 px-1"
> >
{{ displayValue || '---' }} {{ displayValue || '---' }}
</p> </p>

View File

@ -75,7 +75,7 @@ onMounted(() => {
@mousedown="handleMouseDown" @mousedown="handleMouseDown"
> >
<div <div
class="relative max-h-full overflow-auto bg-white shadow-md modal-container rtl:text-right dark:bg-slate-800 skip-context-menu" class="relative max-h-full overflow-auto bg-n-alpha-3 backdrop-blur-[100px] shadow-md modal-container rtl:text-right skip-context-menu"
:class="{ :class="{
'rounded-xl w-[37.5rem]': !fullWidth, 'rounded-xl w-[37.5rem]': !fullWidth,
'items-center rounded-none flex h-full justify-center w-full': 'items-center rounded-none flex h-full justify-center w-full':
@ -101,39 +101,48 @@ onMounted(() => {
<style lang="scss"> <style lang="scss">
.modal-mask { .modal-mask {
@apply flex items-center justify-center bg-modal-backdrop-light dark:bg-modal-backdrop-dark z-[9990] h-full left-0 fixed top-0 w-full; @apply flex items-center justify-center bg-n-alpha-black2 backdrop-blur-[4px] z-[9990] h-full left-0 fixed top-0 w-full;
.modal-container { .modal-container {
&.medium { &.medium {
@apply max-w-[80%] w-[56.25rem]; @apply max-w-[80%] w-[56.25rem];
} }
// .content-box { // .content-box {
// @apply h-auto p-0; // @apply h-auto p-0;
// } // }
.content { .content {
@apply p-8; @apply p-8;
} }
form, form,
.modal-content { .modal-content {
@apply pt-4 pb-8 px-8 self-center; @apply pt-4 pb-8 px-8 self-center;
a { a {
@apply p-4; @apply p-4;
} }
} }
} }
} }
.modal-big { .modal-big {
@apply w-full; @apply w-full;
} }
.modal-mask.right-aligned { .modal-mask.right-aligned {
@apply justify-end; @apply justify-end;
.modal-container { .modal-container {
@apply rounded-none h-full w-[30rem]; @apply rounded-none h-full w-[30rem];
} }
} }
.modal-enter, .modal-enter,
.modal-leave { .modal-leave {
@apply opacity-0; @apply opacity-0;
} }
.modal-enter .modal-container, .modal-enter .modal-container,
.modal-leave .modal-container { .modal-leave .modal-container {
transform: scale(1.1); transform: scale(1.1);

View File

@ -15,6 +15,8 @@ import {
CMD_RESOLVE_CONVERSATION, CMD_RESOLVE_CONVERSATION,
} from 'dashboard/helper/commandbar/events'; } from 'dashboard/helper/commandbar/events';
import Button from 'dashboard/components-next/button/Button.vue';
const store = useStore(); const store = useStore();
const getters = useStoreGetters(); const getters = useStoreGetters();
const { t } = useI18n(); const { t } = useI18n();
@ -41,13 +43,6 @@ const isSnoozed = computed(
() => currentChat.value.status === wootConstants.STATUS_TYPE.SNOOZED () => currentChat.value.status === wootConstants.STATUS_TYPE.SNOOZED
); );
const buttonClass = computed(() => {
if (isPending.value) return 'primary';
if (isOpen.value) return 'success';
if (isResolved.value) return 'warning';
return '';
});
const showAdditionalActions = computed( const showAdditionalActions = computed(
() => !isPending.value && !isSnoozed.value () => !isPending.value && !isSnoozed.value
); );
@ -138,76 +133,77 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);
<template> <template>
<div class="relative flex items-center justify-end resolve-actions"> <div class="relative flex items-center justify-end resolve-actions">
<div class="button-group"> <div
<woot-button class="rounded-lg shadow button-group outline-1 outline"
:class="!showOpenButton ? 'outline-n-container' : 'outline-transparent'"
>
<Button
v-if="isOpen" v-if="isOpen"
class-names="resolve" :label="t('CONVERSATION.HEADER.RESOLVE_ACTION')"
color-scheme="success" size="sm"
icon="checkmark" color="slate"
emoji="✅" class="ltr:rounded-r-none rtl:rounded-l-none !outline-0"
:is-loading="isLoading" :is-loading="isLoading"
@click="onCmdResolveConversation" @click="onCmdResolveConversation"
> />
{{ $t('CONVERSATION.HEADER.RESOLVE_ACTION') }} <Button
</woot-button>
<woot-button
v-else-if="isResolved" v-else-if="isResolved"
class-names="resolve" :label="t('CONVERSATION.HEADER.REOPEN_ACTION')"
color-scheme="warning" size="sm"
icon="arrow-redo" color="slate"
emoji="👀" class="ltr:rounded-r-none rtl:rounded-l-none !outline-0"
:is-loading="isLoading" :is-loading="isLoading"
@click="onCmdOpenConversation" @click="onCmdOpenConversation"
> />
{{ t('CONVERSATION.HEADER.REOPEN_ACTION') }} <Button
</woot-button>
<woot-button
v-else-if="showOpenButton" v-else-if="showOpenButton"
class-names="resolve" :label="t('CONVERSATION.HEADER.OPEN_ACTION')"
color-scheme="primary" size="sm"
icon="person" color="slate"
:is-loading="isLoading" :is-loading="isLoading"
@click="onCmdOpenConversation" @click="onCmdOpenConversation"
> />
{{ t('CONVERSATION.HEADER.OPEN_ACTION') }} <Button
</woot-button>
<woot-button
v-if="showAdditionalActions" v-if="showAdditionalActions"
ref="arrowDownButtonRef" ref="arrowDownButtonRef"
:color-scheme="buttonClass" icon="i-lucide-chevron-down"
:disabled="isLoading" :disabled="isLoading"
icon="chevron-down" size="sm"
emoji="🔽" class="ltr:rounded-l-none rtl:rounded-r-none !outline-0"
color="slate"
trailing-icon
@click="openDropdown" @click="openDropdown"
/> />
</div> </div>
<div <div
v-if="showActionsDropdown" v-if="showActionsDropdown"
v-on-clickaway="closeDropdown" v-on-clickaway="closeDropdown"
class="dropdown-pane dropdown-pane--open left-auto top-[2.625rem] mt-0.5 right-0 max-w-[12.5rem] min-w-[9.75rem]" class="dropdown-pane dropdown-pane--open left-auto top-full mt-0.5 ltr:right-0 rtl:left-0 max-w-[12.5rem] min-w-[9.75rem]"
> >
<WootDropdownMenu class="mb-0"> <WootDropdownMenu class="mb-0">
<WootDropdownItem v-if="!isPending"> <WootDropdownItem v-if="!isPending">
<woot-button <Button
variant="clear" :label="t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL')"
color-scheme="secondary" ghost
size="small" slate
icon="snooze" sm
start
icon="i-lucide-alarm-clock-minus"
class="w-full"
@click="() => openSnoozeModal()" @click="() => openSnoozeModal()"
> />
{{ t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL') }}
</woot-button>
</WootDropdownItem> </WootDropdownItem>
<WootDropdownItem v-if="!isPending"> <WootDropdownItem v-if="!isPending">
<woot-button <Button
variant="clear" :label="t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING')"
color-scheme="secondary" ghost
size="small" slate
icon="book-clock" sm
start
icon="i-lucide-circle-dot-dashed"
class="w-full"
@click="() => toggleStatus(wootConstants.STATUS_TYPE.PENDING)" @click="() => toggleStatus(wootConstants.STATUS_TYPE.PENDING)"
> />
{{ t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
</woot-button>
</WootDropdownItem> </WootDropdownItem>
</WootDropdownMenu> </WootDropdownMenu>
</div> </div>

View File

@ -123,14 +123,18 @@ export default {
} }
&.alert { &.alert {
@apply bg-red-500 dark:bg-red-500; @apply bg-n-ruby-3 text-n-ruby-12;
.banner-action__button { .banner-action__button {
@apply bg-red-700 dark:bg-red-700 border-none text-white dark:text-white; @apply border-none text-n-ruby-12 bg-n-ruby-5;
&:hover { &:hover {
@apply bg-red-800 dark:bg-red-800; @apply bg-n-ruby-4;
} }
} }
a {
@apply text-n-ruby-12;
}
} }
&.warning { &.warning {

View File

@ -110,10 +110,10 @@ export default {
<div <div
v-tooltip.top="{ v-tooltip.top="{
content: tooltipText, content: tooltipText,
delay: { show: 1500, hide: 0 }, delay: { show: 1000, hide: 0 },
hideOnClick: true, hideOnClick: true,
}" }"
class="ml-auto leading-4 text-xxs text-slate-500 dark:text-slate-500 hover:text-slate-900 dark:hover:text-slate-100" class="ml-auto leading-4 text-xxs text-n-slate-10 hover:text-slate-11"
> >
<span>{{ `${createdAtTime}${lastActivityTime}` }}</span> <span>{{ `${createdAtTime}${lastActivityTime}` }}</span>
</div> </div>

View File

@ -20,7 +20,7 @@ export default {
<template> <template>
<div <div
class="inbox--name inline-flex items-center py-0.5 px-0 leading-3 whitespace-nowrap bg-none text-slate-600 dark:text-slate-500 text-xs my-0 mx-2.5" class="inbox--name inline-flex items-center py-0.5 px-0 leading-3 whitespace-nowrap bg-none text-n-slate-11 text-xs my-0 mx-2.5"
> >
<fluent-icon <fluent-icon
class="mr-0.5 rtl:ml-0.5 rtl:mr-0" class="mr-0.5 rtl:ml-0.5 rtl:mr-0"

View File

@ -14,9 +14,7 @@ export default {
</script> </script>
<template> <template>
<div <div class="pt-4 pb-0 px-8 border-b border-solid border-n-weak">
class="bg-slate-25 dark:bg-slate-900 pt-4 pb-0 px-8 border-b border-solid border-slate-50 dark:border-slate-800/50"
>
<h2 class="text-2xl text-slate-800 dark:text-slate-100 mb-1 font-medium"> <h2 class="text-2xl text-slate-800 dark:text-slate-100 mb-1 font-medium">
{{ headerTitle }} {{ headerTitle }}
</h2> </h2>

View File

@ -783,7 +783,7 @@ useEmitter(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, insertContentIntoEditor);
.is-private { .is-private {
.prosemirror-mention-node { .prosemirror-mention-node {
@apply font-medium bg-yellow-100 dark:bg-yellow-800 text-slate-900 dark:text-slate-25 py-0 px-1; @apply font-medium bg-n-amber-2/80 dark:bg-n-amber-2/80 text-slate-900 dark:text-slate-25 py-0 px-1;
} }
.ProseMirror-menubar-wrapper { .ProseMirror-menubar-wrapper {

View File

@ -52,7 +52,7 @@ const translateValue = computed(() => {
<template> <template>
<button <button
class="flex items-center w-auto h-8 p-1 transition-all border rounded-full bg-n-alpha-2 group relative duration-300 ease-in-out" class="flex items-center w-auto h-8 p-1 transition-all border rounded-full bg-n-alpha-2 group relative duration-300 ease-in-out z-0"
@click="$emit('toggleMode')" @click="$emit('toggleMode')"
> >
<div ref="wootEditorReplyMode" class="flex items-center gap-1 px-2 z-20"> <div ref="wootEditorReplyMode" class="flex items-center gap-1 px-2 z-20">
@ -62,11 +62,7 @@ const translateValue = computed(() => {
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }} {{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
</div> </div>
<div <div
class="absolute shadow-sm rounded-full h-6 w-[var(--chip-width)] transition-all duration-300 ease-in-out translate-x-[var(--translate-x)] rtl:translate-x-[var(--rtl-translate-x)]" class="absolute shadow-sm rounded-full h-6 w-[var(--chip-width)] transition-all duration-300 ease-in-out translate-x-[var(--translate-x)] rtl:translate-x-[var(--rtl-translate-x)] bg-n-solid-1"
:class="{
'bg-n-solid-1': !isPrivate,
'bg-n-amber-2': isPrivate,
}"
:style="{ :style="{
'--chip-width': width, '--chip-width': width,
'--translate-x': translateValue, '--translate-x': translateValue,

View File

@ -328,7 +328,7 @@ export default {
<NextButton <NextButton
v-if="hasWhatsappTemplates" v-if="hasWhatsappTemplates"
v-tooltip.top-end="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')" v-tooltip.top-end="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
icon="i-ph-whatsapp" icon="i-ph-whatsapp-logo"
slate slate
faded faded
sm sm

View File

@ -125,10 +125,7 @@ export default {
:show-badge="false" :show-badge="false"
/> />
</woot-tabs> </woot-tabs>
<div <div v-show="!activeIndex" class="flex h-full min-h-0 m-0">
v-show="!activeIndex"
class="flex h-full min-h-0 m-0 bg-slate-25 dark:bg-slate-800"
>
<MessagesView <MessagesView
v-if="currentChat.id" v-if="currentChat.id"
:inbox-id="inboxId" :inbox-id="inboxId"

View File

@ -241,9 +241,9 @@ export default {
<template> <template>
<div <div
class="relative flex items-start flex-grow-0 flex-shrink-0 w-auto max-w-full px-4 py-0 border-t-0 border-b-0 border-l-2 border-r-0 border-transparent border-solid cursor-pointer conversation hover:bg-slate-25 dark:hover:bg-slate-800 group" class="relative flex items-start flex-grow-0 flex-shrink-0 w-auto max-w-full px-4 py-0 border-t-0 border-b-0 border-l-2 border-r-0 border-transparent border-solid cursor-pointer conversation hover:bg-n-alpha-1 dark:hover:bg-n-alpha-3 group"
:class="{ :class="{
'active animate-card-select bg-slate-25 dark:bg-slate-800 border-woot-500': 'active animate-card-select bg-n-alpha-1 dark:bg-n-alpha-3 border-n-weak':
isActiveChat, isActiveChat,
'unread-chat': hasUnread, 'unread-chat': hasUnread,
'has-inbox-name': showInboxName, 'has-inbox-name': showInboxName,
@ -272,57 +272,58 @@ export default {
size="40px" size="40px"
/> />
<div <div
class="px-0 py-3 border-b group-hover:border-transparent flex-1 border-slate-50 dark:border-slate-800/75 w-[calc(100%-40px)]" class="px-0 py-3 border-b group-hover:border-transparent flex-1 border-n-slate-3 w-[calc(100%-40px)]"
> >
<div class="flex justify-between"> <div class="flex justify-between conversation-card--meta">
<InboxName v-if="showInboxName" :inbox="inbox" /> <InboxName v-if="showInboxName" :inbox="inbox" />
<div class="flex gap-2 ml-2 rtl:mr-2 rtl:ml-0"> <div class="flex gap-2 ml-2 rtl:mr-2 rtl:ml-0">
<span <span
v-if="showAssignee && assignee.name" v-if="showAssignee && assignee.name"
class="text-slate-500 dark:text-slate-400 text-xs font-medium leading-3 py-0.5 px-0 inline-flex text-ellipsis overflow-hidden whitespace-nowrap" class="text-n-slate-11 text-xs font-medium leading-3 py-0.5 px-0 inline-flex text-ellipsis overflow-hidden whitespace-nowrap"
> >
<fluent-icon <fluent-icon icon="person" size="12" class="text-n-slate-11" />
icon="person"
size="12"
class="text-slate-500 dark:text-slate-400"
/>
{{ assignee.name }} {{ assignee.name }}
</span> </span>
<PriorityMark :priority="chat.priority" /> <PriorityMark :priority="chat.priority" />
</div> </div>
</div> </div>
<h4 <h4
class="conversation--user text-sm my-0 mx-2 capitalize pt-0.5 text-ellipsis font-medium overflow-hidden whitespace-nowrap w-[calc(100%-70px)] text-slate-900 dark:text-slate-100" class="conversation--user text-sm my-0 mx-2 capitalize pt-0.5 text-ellipsis overflow-hidden whitespace-nowrap w-[calc(100%-70px)] text-n-slate-12"
:class="hasUnread ? 'font-semibold' : 'font-medium'"
> >
{{ currentContact.name }} {{ currentContact.name }}
</h4> </h4>
<MessagePreview <MessagePreview
v-if="lastMessageInChat" v-if="lastMessageInChat"
:message="lastMessageInChat" :message="lastMessageInChat"
class="conversation--message my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] text-sm text-slate-700 dark:text-slate-200" class="conversation--message my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] text-sm"
:class="hasUnread ? 'font-medium text-n-slate-12' : 'text-n-slate-11'"
/> />
<p <p
v-else v-else
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap" class="conversation--message text-n-slate-11 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
:class="hasUnread ? 'font-medium text-n-slate-12' : 'text-n-slate-11'"
> >
<fluent-icon <fluent-icon
size="16" size="16"
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300" class="-mt-0.5 align-middle inline-block text-n-slate-10"
icon="info" icon="info"
/> />
<span> <span>
{{ $t(`CHAT_LIST.NO_MESSAGES`) }} {{ $t(`CHAT_LIST.NO_MESSAGES`) }}
</span> </span>
</p> </p>
<div class="absolute flex flex-col conversation--meta right-4 top-4"> <div
<span class="ml-auto font-normal leading-4 text-black-600 text-xxs"> class="absolute flex flex-col conversation--meta ltr:right-4 rtl:left-4 top-4"
>
<span class="ml-auto font-normal leading-4 text-xxs">
<TimeAgo <TimeAgo
:last-activity-timestamp="chat.timestamp" :last-activity-timestamp="chat.timestamp"
:created-at-timestamp="chat.created_at" :created-at-timestamp="chat.created_at"
/> />
</span> </span>
<span <span
class="unread shadow-lg rounded-full hidden text-xxs font-semibold h-4 leading-4 ml-auto mt-1 min-w-[1rem] px-1 py-0 text-center text-white bg-green-400" class="unread shadow-lg rounded-full hidden text-xxs font-semibold h-4 leading-4 ml-auto mt-1 min-w-[1rem] px-1 py-0 text-center text-white bg-n-teal-9"
> >
{{ unreadCount > 9 ? '9+' : unreadCount }} {{ unreadCount > 9 ? '9+' : unreadCount }}
</span> </span>
@ -362,16 +363,15 @@ export default {
.unread { .unread {
@apply block; @apply block;
} }
.conversation--message {
@apply font-semibold;
}
.conversation--user {
@apply font-semibold;
}
} }
&.compact { &.compact {
@apply pl-0; @apply pl-0;
.conversation-card--meta {
@apply ltr:pr-4 rtl:pl-4;
}
.conversation--details { .conversation--details {
@apply rounded-sm ml-0 pl-5 pr-2; @apply rounded-sm ml-0 pl-5 pr-2;
} }
@ -389,9 +389,11 @@ export default {
&::v-deep .user-thumbnail-box { &::v-deep .user-thumbnail-box {
@apply mt-8; @apply mt-8;
} }
.checkbox-wrapper { .checkbox-wrapper {
@apply mt-8; @apply mt-8;
} }
.conversation--meta { .conversation--meta {
@apply mt-4; @apply mt-4;
} }

View File

@ -130,7 +130,7 @@ export default {
<template> <template>
<div <div
class="flex flex-col items-center justify-between px-4 py-2 bg-white border-b dark:bg-slate-900 border-slate-50 dark:border-slate-800/50 md:flex-row" class="flex flex-col items-center justify-between px-4 py-2 border-b bg-n-background border-n-weak md:flex-row"
> >
<div <div
class="flex flex-col items-center justify-center flex-1 w-full min-w-0" class="flex flex-col items-center justify-center flex-1 w-full min-w-0"
@ -160,9 +160,7 @@ export default {
class="[&>span]:overflow-hidden [&>span]:whitespace-nowrap [&>span]:text-ellipsis min-w-0" class="[&>span]:overflow-hidden [&>span]:whitespace-nowrap [&>span]:text-ellipsis min-w-0"
@click.prevent="$emit('contactPanelToggle')" @click.prevent="$emit('contactPanelToggle')"
> >
<span <span class="text-base font-medium leading-tight text-n-slate-12">
class="text-base font-medium leading-tight text-slate-900 dark:text-slate-100"
>
{{ currentContact.name }} {{ currentContact.name }}
</span> </span>
</woot-button> </woot-button>
@ -170,7 +168,7 @@ export default {
v-if="!isHMACVerified" v-if="!isHMACVerified"
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')" v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
size="14" size="14"
class="text-yellow-600 dark:text-yellow-500 my-0 mx-0 min-w-[14px]" class="text-n-amber-10 my-0 mx-0 min-w-[14px]"
icon="warning" icon="warning"
/> />
</div> </div>
@ -179,10 +177,7 @@ export default {
class="flex items-center gap-2 overflow-hidden text-xs conversation--header--actions text-ellipsis whitespace-nowrap" class="flex items-center gap-2 overflow-hidden text-xs conversation--header--actions text-ellipsis whitespace-nowrap"
> >
<InboxName v-if="hasMultipleInboxes" :inbox="inbox" /> <InboxName v-if="hasMultipleInboxes" :inbox="inbox" />
<span <span v-if="isSnoozed" class="font-medium text-n-amber-10">
v-if="isSnoozed"
class="font-medium text-yellow-600 dark:text-yellow-500"
>
{{ snoozedDisplayText }} {{ snoozedDisplayText }}
</span> </span>
<woot-button <woot-button

View File

@ -53,7 +53,7 @@ const showCopilotTab = computed(() =>
<template> <template>
<div <div
class="ltr:border-l rtl:border-r border-n-weak h-full overflow-hidden z-10 min-w-[300px] w-[300px] 2xl:min-w-96 2xl:w-96 flex flex-col bg-n-background" class="ltr:border-l rtl:border-r border-n-weak h-full overflow-hidden z-10 min-w-[320px] w-[320px] 2xl:min-w-96 2xl:w-96 flex flex-col bg-n-background"
> >
<div v-if="showCopilotTab" class="p-2"> <div v-if="showCopilotTab" class="p-2">
<TabBar <TabBar

View File

@ -31,19 +31,17 @@ export default {
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Hotkey <Hotkey
custom-class="w-8 h-6 text-sm font-medium border-b-2 text-slate-7000 dark:text-slate-100 bg-slate-100 dark:bg-slate-700 border-slate-300 dark:border-slate-500" custom-class="w-8 h-6 text-lg font-medium text-n-slate-12 outline outline-n-container outline-1 bg-n-alpha-3"
> >
</Hotkey> </Hotkey>
<Hotkey <Hotkey
custom-class="w-8 h-6 text-sm font-medium border-b-2 text-slate-700 dark:text-slate-100 bg-slate-100 dark:bg-slate-700 border-slate-300 dark:border-slate-500" custom-class="w-8 h-6 text-xs font-medium text-n-slate-12 outline outline-n-container outline-1 bg-n-alpha-3"
> >
{{ keyShortcut.key }} {{ keyShortcut.key }}
</Hotkey> </Hotkey>
</div> </div>
<span <span class="text-sm font-medium text-center text-n-slate-12">
class="text-sm font-medium text-center text-slate-700 dark:text-slate-300"
>
{{ keyShortcut.description }} {{ keyShortcut.description }}
</span> </span>
</div> </div>

View File

@ -4,6 +4,7 @@ import { ref } from 'vue';
import { useConfig } from 'dashboard/composables/useConfig'; import { useConfig } from 'dashboard/composables/useConfig';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import { useAI } from 'dashboard/composables/useAI'; import { useAI } from 'dashboard/composables/useAI';
import { useMapGetter } from 'dashboard/composables/store';
// components // components
import ReplyBox from './ReplyBox.vue'; import ReplyBox from './ReplyBox.vue';
@ -34,6 +35,7 @@ import { BUS_EVENTS } from 'shared/constants/busEvents';
import { REPLY_POLICY } from 'shared/constants/links'; import { REPLY_POLICY } from 'shared/constants/links';
import wootConstants from 'dashboard/constants/globals'; import wootConstants from 'dashboard/constants/globals';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage'; import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { FEATURE_FLAGS } from '../../../featureFlags';
export default { export default {
components: { components: {
@ -82,8 +84,14 @@ export default {
fetchLabelSuggestions, fetchLabelSuggestions,
} = useAI(); } = useAI();
const showNextBubbles = LocalStorage.get( const currentAccountId = useMapGetter('getCurrentAccountId');
LOCAL_STORAGE_KEYS.USE_NEXT_BUBBLE const isFeatureEnabledonAccount = useMapGetter(
'accounts/isFeatureEnabledonAccount'
);
const showNextBubbles = isFeatureEnabledonAccount.value(
currentAccountId.value,
FEATURE_FLAGS.CHATWOOT_V4
); );
return { return {
@ -465,6 +473,7 @@ export default {
<Banner <Banner
v-if="!currentChat.can_reply" v-if="!currentChat.can_reply"
color-scheme="alert" color-scheme="alert"
class="mt-2 mx-2 rounded-lg overflow-hidden"
:banner-message="replyWindowBannerMessage" :banner-message="replyWindowBannerMessage"
:href-link="replyWindowLink" :href-link="replyWindowLink"
:href-link-text="replyWindowLinkText" :href-link-text="replyWindowLinkText"
@ -474,7 +483,7 @@ export default {
variant="smooth" variant="smooth"
size="tiny" size="tiny"
color-scheme="secondary" color-scheme="secondary"
class="box-border fixed z-10 bg-white border border-r-0 border-solid rounded-bl-calc rtl:rotate-180 rounded-tl-calc dark:bg-slate-700 border-slate-50 dark:border-slate-600" class="box-border fixed z-10 bg-white border border-r-0 border-solid rounded-bl-calc rtl:rotate-180 rounded-tl-calc border-n-weak"
:class="isInboxView ? 'top-52 md:top-40' : 'top-32'" :class="isInboxView ? 'top-52 md:top-40' : 'top-32'"
:icon="isRightOrLeftIcon" :icon="isRightOrLeftIcon"
@click="onToggleContactPanel" @click="onToggleContactPanel"
@ -584,7 +593,7 @@ export default {
class="absolute flex items-center w-full h-0 -top-7" class="absolute flex items-center w-full h-0 -top-7"
> >
<div <div
class="flex py-2 pr-4 pl-5 shadow-md rounded-full bg-white dark:bg-slate-700 text-slate-800 dark:text-slate-100 text-xs font-semibold my-2.5 mx-auto" class="flex py-2 pr-4 pl-5 shadow-md rounded-full bg-white dark:bg-slate-700 text-n-slate-11 text-xs font-semibold my-2.5 mx-auto"
> >
{{ typingUserNames }} {{ typingUserNames }}
<img <img
@ -596,7 +605,6 @@ export default {
</div> </div>
<ReplyBox <ReplyBox
v-model:popout-reply-box="isPopOutReplyBox" v-model:popout-reply-box="isPopOutReplyBox"
:conversation-id="currentChat.id"
@toggle-popout="showPopOutReplyBox" @toggle-popout="showPopOutReplyBox"
/> />
</div> </div>
@ -605,6 +613,7 @@ export default {
<style scoped> <style scoped>
@tailwind components; @tailwind components;
@layer components { @layer components {
.rounded-bl-calc { .rounded-bl-calc {
border-bottom-left-radius: calc(1.5rem + 1px); border-bottom-left-radius: calc(1.5rem + 1px);

View File

@ -4,6 +4,8 @@ import { useAlert } from 'dashboard/composables';
import { emitter } from 'shared/helpers/mitt'; import { emitter } from 'shared/helpers/mitt';
import EmailTranscriptModal from './EmailTranscriptModal.vue'; import EmailTranscriptModal from './EmailTranscriptModal.vue';
import ResolveAction from '../../buttons/ResolveAction.vue'; import ResolveAction from '../../buttons/ResolveAction.vue';
import ButtonV4 from 'dashboard/components-next/button/Button.vue';
import { import {
CMD_MUTE_CONVERSATION, CMD_MUTE_CONVERSATION,
CMD_SEND_TRANSCRIPT, CMD_SEND_TRANSCRIPT,
@ -14,6 +16,7 @@ export default {
components: { components: {
EmailTranscriptModal, EmailTranscriptModal,
ResolveAction, ResolveAction,
ButtonV4,
}, },
data() { data() {
return { return {
@ -51,27 +54,30 @@ export default {
<template> <template>
<div class="relative flex items-center gap-2 actions--container"> <div class="relative flex items-center gap-2 actions--container">
<woot-button <ButtonV4
v-if="!currentChat.muted" v-if="!currentChat.muted"
v-tooltip="$t('CONTACT_PANEL.MUTE_CONTACT')" v-tooltip="$t('CONTACT_PANEL.MUTE_CONTACT')"
variant="clear" size="sm"
color-scheme="secondary" variant="ghost"
icon="speaker-mute" color="slate"
icon="i-lucide-volume-off"
@click="mute" @click="mute"
/> />
<woot-button <ButtonV4
v-else v-else
v-tooltip.left="$t('CONTACT_PANEL.UNMUTE_CONTACT')" v-tooltip.left="$t('CONTACT_PANEL.UNMUTE_CONTACT')"
variant="clear" size="sm"
color-scheme="secondary" variant="ghost"
icon="speaker-1" color="slate"
icon="i-lucide-volume-1"
@click="unmute" @click="unmute"
/> />
<woot-button <ButtonV4
v-tooltip="$t('CONTACT_PANEL.SEND_TRANSCRIPT')" v-tooltip="$t('CONTACT_PANEL.SEND_TRANSCRIPT')"
variant="clear" size="sm"
color-scheme="secondary" variant="ghost"
icon="share" color="slate"
icon="i-lucide-share"
@click="toggleEmailActionsModal" @click="toggleEmailActionsModal"
/> />
<ResolveAction <ResolveAction

View File

@ -40,10 +40,8 @@ export default {
}" }"
class="shrink-0 rounded-sm inline-flex w-3.5 h-3.5" class="shrink-0 rounded-sm inline-flex w-3.5 h-3.5"
:class="{ :class="{
'bg-red-50 dark:bg-red-700 dark:bg-opacity-30 text-red-500 dark:text-red-600': 'bg-n-ruby-5 text-n-ruby-11': isUrgent,
isUrgent, 'bg-n-slate-5 text-n-slate-11': !isUrgent,
'bg-slate-50 dark:bg-slate-700 text-slate-600 dark:text-slate-200':
!isUrgent,
}" }"
> >
<fluent-icon <fluent-icon

View File

@ -1,6 +1,6 @@
<script> <script>
// [TODO] The popout events are needlessly complex and should be simplified // [TODO] The popout events are needlessly complex and should be simplified
import { defineAsyncComponent, defineModel } from 'vue'; import { defineAsyncComponent, defineModel, useTemplateRef } from 'vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { useUISettings } from 'dashboard/composables/useUISettings'; import { useUISettings } from 'dashboard/composables/useUISettings';
@ -80,12 +80,15 @@ export default {
default: false, default: false,
}); });
const replyEditor = useTemplateRef('replyEditor');
return { return {
uiSettings, uiSettings,
popoutReplyBox, popoutReplyBox,
updateUISettings, updateUISettings,
isEditorHotKeyEnabled, isEditorHotKeyEnabled,
fetchSignatureFlagFromUISettings, fetchSignatureFlagFromUISettings,
replyEditor,
}; };
}, },
data() { data() {
@ -553,6 +556,9 @@ export default {
this.$store.dispatch('draftMessages/delete', { key }); this.$store.dispatch('draftMessages/delete', { key });
} }
}, },
getElementToBind() {
return this.replyEditor;
},
getKeyboardEvents() { getKeyboardEvents() {
return { return {
Escape: { Escape: {
@ -1081,7 +1087,7 @@ export default {
:action-button-label="$t('CONVERSATION.ASSIGN_TO_ME')" :action-button-label="$t('CONVERSATION.ASSIGN_TO_ME')"
@primary-action="onClickSelfAssign" @primary-action="onClickSelfAssign"
/> />
<div class="reply-box" :class="replyBoxClass"> <div ref="replyEditor" class="reply-box" :class="replyBoxClass">
<ReplyTopPanel <ReplyTopPanel
:mode="replyType" :mode="replyType"
:is-message-length-reaching-threshold="isMessageLengthReachingThreshold" :is-message-length-reaching-threshold="isMessageLengthReachingThreshold"

View File

@ -1,8 +1,12 @@
<script> <script>
import { validEmailsByComma } from './helpers/emailHeadHelper'; import { validEmailsByComma } from './helpers/emailHeadHelper';
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import ButtonV4 from 'dashboard/components-next/button/Button.vue';
export default { export default {
components: {
ButtonV4,
},
props: { props: {
ccEmails: { ccEmails: {
type: String, type: String,
@ -116,14 +120,14 @@ export default {
@blur="onBlur" @blur="onBlur"
/> />
</div> </div>
<woot-button <ButtonV4
v-if="!showBcc" v-if="!showBcc"
variant="clear" :label="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.ADD_BCC')"
size="small" ghost
xs
primary
@click="handleAddBcc" @click="handleAddBcc"
> />
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.ADD_BCC') }}
</woot-button>
</div> </div>
<span v-if="v$.ccEmailsVal.$error" class="message"> <span v-if="v$.ccEmailsVal.$error" class="message">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.ERROR') }} {{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.ERROR') }}
@ -156,10 +160,10 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.input-group-wrap .message { .input-group-wrap .message {
@apply text-sm text-red-500 dark:text-red-500; @apply text-sm text-n-ruby-8;
} }
.input-group { .input-group {
@apply border-b border-solid border-slate-75 dark:border-slate-700 my-1 flex items-center gap-2; @apply border-b border-solid border-n-weak my-1 flex items-center gap-2;
.input-group-label { .input-group-label {
@apply border-transparent bg-transparent text-xs font-semibold pl-0; @apply border-transparent bg-transparent text-xs font-semibold pl-0;
@ -167,9 +171,9 @@ export default {
} }
.input-group.error { .input-group.error {
@apply border-b-red-500 dark:border-b-red-500; @apply border-n-ruby-8;
.input-group-label { .input-group-label {
@apply text-red-500 dark:text-red-500; @apply text-n-ruby-8;
} }
} }
</style> </style>

View File

@ -1,4 +1,5 @@
<script setup> <script setup>
import Avatar from 'next/avatar/Avatar.vue';
import { ref, computed, watch, nextTick } from 'vue'; import { ref, computed, watch, nextTick } from 'vue';
import { useStoreGetters } from 'dashboard/composables/store'; import { useStoreGetters } from 'dashboard/composables/store';
import { useKeyboardNavigableList } from 'dashboard/composables/useKeyboardNavigableList'; import { useKeyboardNavigableList } from 'dashboard/composables/useKeyboardNavigableList';
@ -67,7 +68,7 @@ const onAgentSelect = index => {
<ul <ul
v-if="items.length" v-if="items.length"
ref="tagAgentsRef" ref="tagAgentsRef"
class="vertical dropdown menu mention--box bg-white text-sm dark:bg-slate-700 rounded-md overflow-auto absolute w-full z-20 pt-2 px-2 pb-0 shadow-md left-0 leading-[1.2] bottom-full max-h-[12.5rem] border-t border-solid border-slate-75 dark:border-slate-800" class="vertical dropdown menu mention--box bg-n-solid-1 p-1 rounded-xl text-sm overflow-auto absolute w-full z-20 shadow-md left-0 leading-[1.2] bottom-full max-h-[12.5rem] border border-solid border-n-strong"
:class="{ :class="{
'border-b-[0.5rem] border-solid border-white dark:!border-slate-700': 'border-b-[0.5rem] border-solid border-white dark:!border-slate-700':
items.length <= 4, items.length <= 4,
@ -78,35 +79,31 @@ const onAgentSelect = index => {
:id="`mention-item-${index}`" :id="`mention-item-${index}`"
:key="agent.id" :key="agent.id"
:class="{ :class="{
'bg-slate-50 dark:bg-slate-800': index === selectedIndex, 'bg-n-alpha-black2': index === selectedIndex,
'last:mb-0': items.length <= 4, 'last:mb-0': items.length <= 4,
}" }"
class="flex items-center p-2 rounded-md last:mb-2" class="flex items-center px-2 py-1 rounded-md"
@click="onAgentSelect(index)" @click="onAgentSelect(index)"
@mouseover="onHover(index)" @mouseover="onHover(index)"
> >
<div class="mr-2"> <div class="mr-2">
<woot-thumbnail <Avatar :src="agent.thumbnail" :name="agent.name" rounded-full />
:src="agent.thumbnail"
:username="agent.name"
size="32px"
/>
</div> </div>
<div <div
class="flex-1 max-w-full overflow-hidden whitespace-nowrap text-ellipsis" class="flex-1 max-w-full overflow-hidden whitespace-nowrap text-ellipsis"
> >
<h5 <h5
class="mb-0 overflow-hidden text-sm text-slate-800 dark:text-slate-100 whitespace-nowrap text-ellipsis" class="mb-0 overflow-hidden text-sm text-n-slate-11 whitespace-nowrap text-ellipsis"
:class="{ :class="{
'text-slate-900 dark:text-slate-100': index === selectedIndex, 'text-n-slate-12': index === selectedIndex,
}" }"
> >
{{ agent.name }} {{ agent.name }}
</h5> </h5>
<div <div
class="overflow-hidden text-xs whitespace-nowrap text-ellipsis text-slate-700 dark:text-slate-300" class="overflow-hidden text-xs whitespace-nowrap text-ellipsis text-n-slate-10"
:class="{ :class="{
'text-slate-800 dark:text-slate-200': index === selectedIndex, 'text-n-slate-11': index === selectedIndex,
}" }"
> >
{{ agent.email }} {{ agent.email }}

View File

@ -47,9 +47,7 @@ export default {
return this.slaStatus?.isSlaMissed; return this.slaStatus?.isSlaMissed;
}, },
slaTextStyles() { slaTextStyles() {
return this.isSlaMissed return this.isSlaMissed ? 'text-n-ruby-11' : 'text-n-amber-11';
? 'text-red-400 dark:text-red-300'
: 'text-yellow-600 dark:text-yellow-500';
}, },
slaStatusText() { slaStatusText() {
const upperCaseType = this.slaStatus?.type?.toUpperCase(); // FRT, NRT, or RT const upperCaseType = this.slaStatus?.type?.toUpperCase(); // FRT, NRT, or RT
@ -107,8 +105,12 @@ export default {
<template> <template>
<div <div
v-if="hasSlaThreshold" v-if="hasSlaThreshold"
class="relative flex items-center border cursor-pointer min-w-fit border-slate-100 dark:border-slate-700" class="relative flex items-center cursor-pointer min-w-fit"
:class="showExtendedInfo ? 'h-[26px] rounded-lg' : 'rounded h-5'" :class="
showExtendedInfo
? 'h-[26px] rounded-lg bg-n-alpha-1'
: 'rounded h-5 border border-n-strong'
"
> >
<div <div
v-on-clickaway="closeSlaPopover" v-on-clickaway="closeSlaPopover"
@ -120,7 +122,7 @@ export default {
class="flex items-center gap-1" class="flex items-center gap-1"
:class=" :class="
showExtendedInfo && showExtendedInfo &&
'ltr:pr-1.5 rtl:pl-1.5 ltr:border-r rtl:border-l border-solid border-slate-100 dark:border-slate-700' 'ltr:pr-1.5 rtl:pl-1.5 ltr:border-r rtl:border-l border-n-strong'
" "
> >
<fluent-icon <fluent-icon

View File

@ -73,36 +73,36 @@ const variableKey = (item = {}) => {
<template> <template>
<div <div
ref="mentionsListContainerRef" ref="mentionsListContainerRef"
class="bg-white dark:bg-slate-800 rounded-md overflow-auto absolute w-full z-20 pb-0 shadow-md left-0 bottom-full max-h-[9.75rem] border border-solid border-slate-100 dark:border-slate-700 mention--box" class="bg-n-solid-1 p-1 rounded-xl overflow-auto absolute w-full z-20 shadow-md left-0 bottom-full max-h-[9.75rem] border border-solid border-n-strong mention--box"
> >
<ul class="mb-0 vertical dropdown menu"> <ul class="mb-0 vertical dropdown menu">
<woot-dropdown-item <woot-dropdown-item
v-for="(item, index) in items" v-for="(item, index) in items"
:id="`mention-item-${index}`" :id="`mention-item-${index}`"
:key="item.key" :key="item.key"
class="!mb-0" class="!mb-1"
@mouseover="onHover(index)" @mouseover="onHover(index)"
> >
<button <button
class="flex group flex-col gap-0.5 overflow-hidden cursor-pointer items-start rounded-none py-2.5 px-2.5 justify-center w-full h-full text-left hover:bg-woot-50 dark:hover:bg-woot-800 border-x-0 border-t-0 border-b border-solid border-slate-100 dark:border-slate-700" class="flex rounded-lg group flex-col gap-0.5 overflow-hidden cursor-pointer items-start px-3 py-2 justify-center w-full h-full text-left hover:bg-n-alpha-black2"
:class="{ :class="{
' bg-woot-25 dark:bg-woot-800': index === selectedIndex, 'bg-n-alpha-black2': index === selectedIndex,
}" }"
@click="onListItemSelection(index)" @click="onListItemSelection(index)"
> >
<slot :item="item" :index="index" :selected="index === selectedIndex"> <slot :item="item" :index="index" :selected="index === selectedIndex">
<p <p
class="max-w-full min-w-0 mb-0 overflow-hidden text-sm font-medium text-slate-900 dark:text-slate-100 group-hover:text-woot-500 dark:group-hover:text-woot-500 text-ellipsis whitespace-nowrap" class="max-w-full min-w-0 mb-0 overflow-hidden text-sm font-medium text-n-slate-11 group-hover:text-n-slate-11 text-ellipsis whitespace-nowrap"
:class="{ :class="{
'text-woot-500 dark:text-woot-500': index === selectedIndex, 'text-n-slate-12': index === selectedIndex,
}" }"
> >
{{ item.description }} {{ item.description }}
</p> </p>
<p <p
class="max-w-full min-w-0 mb-0 overflow-hidden text-xs text-slate-500 dark:text-slate-300 group-hover:text-woot-500 dark:group-hover:text-woot-500 text-ellipsis whitespace-nowrap" class="max-w-full min-w-0 mb-0 overflow-hidden text-xs text-slate-500 dark:text-slate-300 group-hover:text-n-slate-11 text-ellipsis whitespace-nowrap"
:class="{ :class="{
'text-woot-500 dark:text-woot-500': index === selectedIndex, 'text-n-slate-12': index === selectedIndex,
}" }"
> >
{{ variableKey(item) }} {{ variableKey(item) }}

View File

@ -5,5 +5,4 @@ export const LOCAL_STORAGE_KEYS = {
COLOR_SCHEME: 'color_scheme', COLOR_SCHEME: 'color_scheme',
DISMISSED_LABEL_SUGGESTIONS: 'labelSuggestionsDismissed', DISMISSED_LABEL_SUGGESTIONS: 'labelSuggestionsDismissed',
MESSAGE_REPLY_TO: 'messageReplyTo', MESSAGE_REPLY_TO: 'messageReplyTo',
USE_NEXT_BUBBLE: 'useNextBubble',
}; };

View File

@ -274,7 +274,7 @@ export default {
} }
hr { hr {
@apply m-1 border-b border-solid border-slate-50 dark:border-slate-800/50; @apply m-1 border-b border-solid border-n-strong;
} }
} }

View File

@ -71,12 +71,9 @@ export default {
</script> </script>
<template> <template>
<blockquote <blockquote ref="messageContainer" class="message border-l-2 border-n-weak">
ref="messageContainer"
class="message border-l-2 border-slate-100 dark:border-slate-700"
>
<p class="header"> <p class="header">
<strong class="text-slate-700 dark:text-slate-100"> <strong class="text-n-slate-11">
{{ author }} {{ author }}
</strong> </strong>
{{ $t('SEARCH.WROTE') }} {{ $t('SEARCH.WROTE') }}
@ -93,18 +90,18 @@ export default {
} }
.message-content::v-deep p, .message-content::v-deep p,
.message-content::v-deep li::marker { .message-content::v-deep li::marker {
@apply text-slate-700 dark:text-slate-100 mb-1; @apply text-n-slate-11 mb-1;
} }
.header { .header {
@apply text-slate-500 dark:text-slate-300 mb-1; @apply text-n-slate-11 mb-1;
} }
.message-content { .message-content {
@apply break-words text-slate-600 dark:text-slate-200; @apply break-words text-n-slate-11;
} }
.message-content::v-deep .searchkey--highlight { .message-content::v-deep .searchkey--highlight {
@apply text-woot-600 dark:text-woot-500 text-sm font-semibold; @apply text-n-slate-12 text-sm font-semibold;
} }
</style> </style>

View File

@ -1,63 +1,35 @@
<script> <script setup>
export default { defineProps({
props: { shrink: {
shrink: { type: Boolean,
type: Boolean, default: false,
default: false,
},
}, },
emits: ['expand'], });
};
defineEmits(['expand']);
</script> </script>
<template> <template>
<div class="read-more"> <div>
<div <div
:class="{ :class="{
'shrink-container after:shrink-gradient-light dark:after:shrink-gradient-dark': 'max-h-[100px] overflow-hidden relative': shrink,
shrink,
}" }"
> >
<slot /> <slot />
<woot-button <div
v-if="shrink" class="bg-n-slate-3 rounded-md dark:bg-n-solid-3 absolute left-0 right-0 z-20 mx-auto mt-0 max-w-max bottom-2 backdrop-blur[100px]"
size="tiny"
variant="smooth"
color-scheme="primary"
class="read-more-button"
@click.prevent="$emit('expand')"
> >
{{ $t('SEARCH.READ_MORE') }} <woot-button
</woot-button> v-if="shrink"
size="tiny"
variant="smooth"
color-scheme="primary"
@click.prevent="$emit('expand')"
>
{{ $t('SEARCH.READ_MORE') }}
</woot-button>
</div>
</div> </div>
</div> </div>
</template> </template>
<style scoped>
@tailwind components;
@layer components {
.shrink-gradient-light {
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 1) 100%
);
}
.shrink-gradient-dark {
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0),
rgb(21, 23, 24) 100%
);
}
}
.shrink-container {
@apply max-h-[100px] overflow-hidden relative;
}
.shrink-container::after {
}
.read-more-button {
@apply max-w-max absolute bottom-2 left-0 right-0 mx-auto mt-0 z-20 shadow-sm;
}
</style>

View File

@ -50,14 +50,28 @@ export default {
</script> </script>
<template> <template>
<div class="input-container" :class="{ 'is-focused': isInputFocused }"> <div
<div class="icon-container"> class="input-container rounded-xl transition-[border-bottom] duration-[0.2s] ease-[ease-in-out] relative flex items-center py-2 px-4 h-14 gap-2 border border-solid"
<fluent-icon icon="search" class="icon" aria-hidden="true" /> :class="{
'border-n-brand': isInputFocused,
'border-n-weak': !isInputFocused,
}"
>
<div class="flex items-center">
<fluent-icon
icon="search"
class="icon"
aria-hidden="true"
:class="{
'text-n-blue-text': isInputFocused,
'text-n-slate-10': !isInputFocused,
}"
/>
</div> </div>
<input <input
ref="searchInput" ref="searchInput"
type="search" type="search"
class="dark:bg-slate-900" class="w-full m-0 bg-transparent border-transparent shadow-none text-n-slate-12 dark:text-n-slate-12 active:border-transparent active:shadow-none hover:border-transparent hover:shadow-none focus:border-transparent focus:shadow-none"
:placeholder="$t('SEARCH.INPUT_PLACEHOLDER')" :placeholder="$t('SEARCH.INPUT_PLACEHOLDER')"
:value="searchQuery" :value="searchQuery"
@focus="onFocus" @focus="onFocus"
@ -68,37 +82,7 @@ export default {
:title="$t('SEARCH.PLACEHOLDER_KEYBINDING')" :title="$t('SEARCH.PLACEHOLDER_KEYBINDING')"
:show-close="false" :show-close="false"
small small
class="helper-label" class="!m-0 whitespace-nowrap !bg-n-slate-3 dark:!bg-n-solid-3 !border-n-weak dark:!border-n-strong"
/> />
</div> </div>
</template> </template>
<style lang="scss" scoped>
.input-container {
transition: border-bottom 0.2s ease-in-out;
@apply relative flex items-center py-2 px-4 h-14 gap-2 rounded-sm border border-solid border-slate-100 dark:border-slate-800;
input[type='search'] {
@apply w-full m-0 shadow-none border-transparent active:border-transparent active:shadow-none hover:border-transparent hover:shadow-none focus:border-transparent focus:shadow-none;
}
&.is-focused {
@apply border-woot-100 dark:border-woot-600;
.icon {
color: var(--w-400);
@apply text-woot-400 dark:text-woot-500;
}
}
}
.icon-container {
@apply flex items-center;
.icon {
@apply text-slate-400;
}
}
.helper-label {
@apply m-0 whitespace-nowrap;
}
</style>

View File

@ -1,69 +1,65 @@
<script> <script setup>
import { frontendURL } from 'dashboard/helper/URLHelper'; import { frontendURL } from 'dashboard/helper/URLHelper';
export default { import { computed } from 'vue';
props: {
id: { const props = defineProps({
type: [String, Number], id: {
default: 0, type: [String, Number],
}, default: 0,
email: {
type: String,
default: '',
},
phone: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
thumbnail: {
type: String,
default: '',
},
accountId: {
type: [String, Number],
default: 0,
},
}, },
computed: { email: {
navigateTo() { type: String,
return frontendURL(`accounts/${this.accountId}/contacts/${this.id}`); default: '',
},
}, },
}; phone: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
thumbnail: {
type: String,
default: '',
},
accountId: {
type: [String, Number],
default: 0,
},
});
const navigateTo = computed(() => {
return frontendURL(`accounts/${props.accountId}/contacts/${props.id}`);
});
</script> </script>
<template> <template>
<router-link :to="navigateTo" class="contact-item"> <router-link
:to="navigateTo"
class="flex items-center p-2 rounded-md cursor-pointer hover:bg-n-slate-3 dark:hover:bg-n-solid-3"
>
<woot-thumbnail :src="thumbnail" :username="name" size="24px" /> <woot-thumbnail :src="thumbnail" :username="name" size="24px" />
<div class="ml-2 rtl:mr-2 rtl:ml-0"> <div class="ml-2 rtl:mr-2 rtl:ml-0">
<h5 class="text-sm name text-slate-800 dark:text-slate-200"> <h5 class="text-sm name text-n-slate-12 dark:text-n-slate-12">
{{ name }} {{ name }}
</h5> </h5>
<p <p
class="m-0 text-slate-600 dark:text-slate-200 gap-1 text-sm flex items-center" class="flex items-center gap-1 m-0 text-sm text-slate-600 dark:text-slate-200"
> >
<span v-if="email" class="email text-slate-800 dark:text-slate-200">{{ <span v-if="email" class="email text-n-slate-12 dark:text-n-slate-12">{{
email email
}}</span> }}</span>
<span v-if="phone" class="separator text-slate-700 dark:text-slate-200"> <span
v-if="phone"
class="separator text-n-slate-10 dark:text-n-slate-10"
>
</span> </span>
<span v-if="phone" class="phone text-slate-800 dark:text-slate-200"> <span v-if="phone" class="phone text-n-slate-12 dark:text-n-slate-12">
{{ phone }} {{ phone }}
</span> </span>
</p> </p>
</div> </div>
</router-link> </router-link>
</template> </template>
<style scoped lang="scss">
.contact-item {
@apply cursor-pointer flex items-center p-2 rounded-sm hover:bg-slate-25 dark:hover:bg-slate-800;
}
.contact-details {
@apply ml-2 rtl:mr-2 rtl:ml-0;
}
</style>

View File

@ -43,7 +43,7 @@ export default {
:show-title="showTitle" :show-title="showTitle"
:is-fetching="isFetching" :is-fetching="isFetching"
> >
<ul v-if="contacts.length" class="search-list"> <ul v-if="contacts.length" class="space-y-1.5">
<li v-for="contact in contacts" :key="contact.id"> <li v-for="contact in contacts" :key="contact.id">
<SearchResultContactItem <SearchResultContactItem
:id="contact.id" :id="contact.id"

View File

@ -56,37 +56,62 @@ export default {
</script> </script>
<template> <template>
<router-link :to="navigateTo" class="conversation-item"> <router-link
<div class="icon-wrap"> :to="navigateTo"
class="flex p-2 rounded-md cursor-pointer hover:bg-n-slate-3 dark:hover:bg-n-solid-3"
>
<div
class="flex items-center justify-center flex-shrink-0 w-6 h-6 rounded bg-n-brand/10 dark:bg-n-brand/40 text-n-blue-text dark:text-n-blue-text"
>
<fluent-icon icon="chat-multiple" :size="14" /> <fluent-icon icon="chat-multiple" :size="14" />
</div> </div>
<div class="conversation-details"> <div class="flex-grow min-w-0 ml-2">
<div class="meta-wrap"> <div class="flex items-center justify-between mb-1">
<div class="flex"> <div class="flex">
<woot-label <woot-label
class="conversation-id" class="!bg-n-slate-3 dark:!bg-n-solid-3 !border-n-weak dark:!border-n-strong m-0"
:title="`#${id}`" :title="`#${id}`"
:show-close="false" :show-close="false"
small small
/> />
<div class="inbox-name-wrap"> <div
<InboxName :inbox="inbox" class="mr-2 rtl:mr-0 rtl:ml-2" /> class="flex items-center justify-center h-5 ml-1 rounded bg-n-slate-3 dark:bg-n-solid-3 w-fit rtl:ml-0 rtl:mr-1"
>
<InboxName
:inbox="inbox"
class="mr-2 rtl:mr-0 rtl:ml-2 bg-n-slate-3 dark:bg-n-solid-3 text-n-slate-11 dark:text-n-slate-11"
/>
</div> </div>
</div> </div>
<div> <div>
<span class="created-at">{{ createdAtTime }}</span> <span
class="text-xs font-normal text-n-slate-11 dark:text-n-slate-11"
>
{{ createdAtTime }}
</span>
</div> </div>
</div> </div>
<div class="user-details"> <div class="flex gap-2">
<h5 v-if="name" class="text-sm name text-slate-800 dark:text-slate-100"> <h5
<span class="pre-text"> {{ $t('SEARCH.FROM') }}: </span> v-if="name"
class="m-0 text-sm text-n-slate-12 dark:text-n-slate-12"
>
<span
class="text-xs font-normal text-n-slate-11 dark:text-n-slate-11"
>
{{ $t('SEARCH.FROM') }}:
</span>
{{ name }} {{ name }}
</h5> </h5>
<h5 <h5
v-if="email" v-if="email"
class="overflow-hidden text-sm email text-slate-700 dark:text-slate-200 whitespace-nowrap text-ellipsis" class="m-0 overflow-hidden text-sm text-n-slate-12 dark:text-n-slate-12 whitespace-nowrap text-ellipsis"
> >
<span class="pre-text">{{ $t('SEARCH.EMAIL') }}:</span> <span
class="text-xs font-normal text-n-slate-11 dark:text-n-slate-11"
>
{{ $t('SEARCH.EMAIL') }}:
</span>
{{ email }} {{ email }}
</h5> </h5>
</div> </div>
@ -94,40 +119,3 @@ export default {
</div> </div>
</router-link> </router-link>
</template> </template>
<style scoped lang="scss">
.conversation-item {
@apply cursor-pointer flex p-2 rounded hover:bg-slate-25 dark:hover:bg-slate-800;
}
.meta-wrap {
@apply flex items-center justify-between mb-1;
}
.icon-wrap {
@apply w-6 h-6 flex-shrink-0 bg-woot-75 dark:bg-woot-600/50 flex items-center justify-center rounded text-woot-600 dark:text-woot-500;
}
.inbox-name-wrap {
@apply bg-slate-25 dark:bg-slate-800 h-5 flex justify-center items-center rounded w-fit ml-1 rtl:ml-0 rtl:mr-1;
}
.conversation-details {
@apply ml-2 flex-grow min-w-0;
}
.name {
@apply flex-shrink-0;
}
.conversation-id,
.name,
.email {
@apply m-0;
}
.created-at,
.pre-text {
@apply text-slate-600 dark:text-slate-100 text-xs font-normal;
}
.user-details {
@apply flex gap-2;
}
</style>

View File

@ -42,7 +42,7 @@ export default {
:show-title="showTitle || isFetching" :show-title="showTitle || isFetching"
:is-fetching="isFetching" :is-fetching="isFetching"
> >
<ul v-if="conversations.length" class="search-list"> <ul v-if="conversations.length" class="space-y-1.5">
<li v-for="conversation in conversations" :key="conversation.id"> <li v-for="conversation in conversations" :key="conversation.id">
<SearchResultConversationItem <SearchResultConversationItem
:id="conversation.id" :id="conversation.id"

View File

@ -51,7 +51,7 @@ export default {
:show-title="showTitle" :show-title="showTitle"
:is-fetching="isFetching" :is-fetching="isFetching"
> >
<ul v-if="messages.length" class="search-list"> <ul v-if="messages.length" class="space-y-1.5">
<li v-for="message in messages" :key="message.id"> <li v-for="message in messages" :key="message.id">
<SearchResultConversationItem <SearchResultConversationItem
:id="message.conversation_id" :id="message.conversation_id"

View File

@ -1,72 +1,50 @@
<script> <script setup>
export default { import { computed } from 'vue';
props: {
title: { const props = defineProps({
type: String, title: {
default: '', type: String,
}, default: '',
empty: {
type: Boolean,
default: false,
},
query: {
type: String,
default: '',
},
showTitle: {
type: Boolean,
default: true,
},
isFetching: {
type: Boolean,
default: true,
},
}, },
computed: { empty: {
titleCase() { type: Boolean,
return this.title.toLowerCase(); default: false,
},
}, },
}; query: {
type: String,
default: '',
},
showTitle: {
type: Boolean,
default: true,
},
isFetching: {
type: Boolean,
default: true,
},
});
const titleCase = computed(() => props.title.toLowerCase());
</script> </script>
<template> <template>
<section class="result-section"> <section class="mx-0 my-2">
<div v-if="showTitle" class="header"> <div v-if="showTitle" class="sticky top-0 p-2 z-50 mb-0.5 bg-n-background">
<h3 class="text-sm text-slate-800 dark:text-slate-100">{{ title }}</h3> <h3 class="text-sm text-n-slate-12">{{ title }}</h3>
</div> </div>
<woot-loading-state <woot-loading-state
v-if="isFetching" v-if="isFetching"
:message="$t('SEARCH.SEARCHING_DATA')" :message="$t('SEARCH.SEARCHING_DATA')"
/> />
<slot v-else /> <slot v-else />
<div v-if="empty && !isFetching" class="empty"> <div
<fluent-icon icon="info" size="16px" class="icon" /> v-if="empty && !isFetching"
<p class="empty-state__text"> class="flex items-center justify-center px-4 py-6 m-2 rounded-md bg-n-slate-2 dark:bg-n-solid-3"
>
<fluent-icon icon="info" size="16px" class="text-n-slate-11" />
<p class="mx-2 my-0 text-center text-n-slate-11">
{{ $t('SEARCH.EMPTY_STATE', { item: titleCase, query }) }} {{ $t('SEARCH.EMPTY_STATE', { item: titleCase, query }) }}
</p> </p>
</div> </div>
</section> </section>
</template> </template>
<style scoped lang="scss">
.result-section {
@apply my-2 mx-0;
}
.search-list {
@apply m-0 py-4 px-0 list-none;
}
.header {
@apply sticky top-0 p-2 z-50 bg-white dark:bg-slate-900 mb-0.5;
}
.empty {
@apply flex items-center justify-center py-6 px-4 m-2 bg-slate-25 dark:bg-slate-800 rounded-md;
.icon {
@apply text-slate-500 dark:text-slate-300;
}
.empty-state__text {
@apply text-slate-500 dark:text-slate-300 text-center my-0 mx-2;
}
}
</style>

View File

@ -4,6 +4,7 @@ import SearchTabs from './SearchTabs.vue';
import SearchResultConversationsList from './SearchResultConversationsList.vue'; import SearchResultConversationsList from './SearchResultConversationsList.vue';
import SearchResultMessagesList from './SearchResultMessagesList.vue'; import SearchResultMessagesList from './SearchResultMessagesList.vue';
import SearchResultContactsList from './SearchResultContactsList.vue'; import SearchResultContactsList from './SearchResultContactsList.vue';
import ButtonV4 from 'dashboard/components-next/button/Button.vue';
import { useTrack } from 'dashboard/composables'; import { useTrack } from 'dashboard/composables';
import Policy from 'dashboard/components/policy.vue'; import Policy from 'dashboard/components/policy.vue';
import { import {
@ -27,6 +28,7 @@ export default {
SearchResultConversationsList, SearchResultConversationsList,
SearchResultMessagesList, SearchResultMessagesList,
Policy, Policy,
ButtonV4,
}, },
data() { data() {
return { return {
@ -201,19 +203,20 @@ export default {
</script> </script>
<template> <template>
<div class="search-page"> <div class="flex flex-col w-full bg-n-background">
<div class="page-header"> <div class="flex p-4">
<woot-button <ButtonV4
icon="chevron-left" :label="$t('GENERAL_SETTINGS.BACK')"
variant="smooth" icon="i-lucide-chevron-left"
size="small " faded
class="back-button" primary
sm
@click="onBack" @click="onBack"
> />
{{ $t('GENERAL_SETTINGS.BACK') }}
</woot-button>
</div> </div>
<section class="search-root"> <section
class="flex my-0 p-4 relative mx-auto max-w-[45rem] min-h-[20rem] flex-col w-full h-full bg-n-background"
>
<header> <header>
<SearchHeader @search="onSearch" /> <SearchHeader @search="onSearch" />
<SearchTabs <SearchTabs
@ -223,7 +226,7 @@ export default {
@tab-change="tab => (selectedTab = tab)" @tab-change="tab => (selectedTab = tab)"
/> />
</header> </header>
<div class="search-results"> <div class="flex-grow h-full px-2 py-0 overflow-y-auto">
<div v-if="showResultsSection"> <div v-if="showResultsSection">
<Policy :permissions="[...rolePermissions, contactPermissions]"> <Policy :permissions="[...rolePermissions, contactPermissions]">
<SearchResultContactsList <SearchResultContactsList
@ -259,17 +262,23 @@ export default {
/> />
</Policy> </Policy>
</div> </div>
<div v-else-if="showEmptySearchResults" class="empty"> <div
<fluent-icon icon="info" size="16px" class="icon" /> v-else-if="showEmptySearchResults"
<p class="empty-state__text"> class="flex flex-col items-center justify-center px-4 py-6 mt-8 rounded-md"
>
<fluent-icon icon="info" size="16px" class="text-n-slate-11" />
<p class="m-2 text-center text-n-slate-11">
{{ $t('SEARCH.EMPTY_STATE_FULL', { query }) }} {{ $t('SEARCH.EMPTY_STATE_FULL', { query }) }}
</p> </p>
</div> </div>
<div v-else class="text-center empty"> <div
v-else
class="flex flex-col items-center justify-center px-4 py-6 mt-8 text-center rounded-md"
>
<p class="text-center margin-bottom-0"> <p class="text-center margin-bottom-0">
<fluent-icon icon="search" size="24px" class="icon" /> <fluent-icon icon="search" size="24px" class="text-n-slate-11" />
</p> </p>
<p class="empty-state__text"> <p class="m-2 text-center text-n-slate-11">
{{ $t('SEARCH.EMPTY_STATE_DEFAULT') }} {{ $t('SEARCH.EMPTY_STATE_DEFAULT') }}
</p> </p>
</div> </div>
@ -277,29 +286,3 @@ export default {
</section> </section>
</div> </div>
</template> </template>
<style lang="scss" scoped>
.search-page {
@apply flex flex-col w-full bg-white dark:bg-slate-900;
}
.page-header {
@apply flex p-4;
}
.search-root {
@apply flex my-0 p-4 relative mx-auto max-w-[45rem] min-h-[20rem] flex-col w-full h-full bg-white dark:bg-slate-900;
.search-results {
@apply flex-grow h-full overflow-y-auto py-0 px-2;
}
}
.empty {
@apply flex flex-col items-center justify-center py-6 px-4 rounded-md mt-8;
.icon {
@apply text-slate-500 dark:text-slate-400;
}
.empty-state__text {
@apply text-center text-slate-500 dark:text-slate-400 m-2;
}
}
</style>

View File

@ -98,18 +98,19 @@ onMounted(() => {
:channel-type="channelType" :channel-type="channelType"
@toggle-panel="onPanelToggle" @toggle-panel="onPanelToggle"
/> />
<div class="list-group"> <div class="list-group pb-8">
<Draggable <Draggable
:list="conversationSidebarItems" :list="conversationSidebarItems"
animation="200" animation="200"
ghost-class="ghost" ghost-class="ghost"
handle=".drag-handle" handle=".drag-handle"
item-key="name" item-key="name"
class="flex flex-col gap-3"
@start="dragging = true" @start="dragging = true"
@end="onDragEnd" @end="onDragEnd"
> >
<template #item="{ element }"> <template #item="{ element }">
<div :key="element.name" class="bg-white dark:bg-gray-800"> <div :key="element.name" class="px-2">
<div <div
v-if="element.name === 'conversation_actions'" v-if="element.name === 'conversation_actions'"
class="conversation--actions" class="conversation--actions"
@ -227,10 +228,12 @@ onMounted(() => {
.contact--profile { .contact--profile {
@apply pb-3 border-b border-solid border-slate-75 dark:border-slate-700; @apply pb-3 border-b border-solid border-slate-75 dark:border-slate-700;
} }
.conversation--actions .multiselect-wrap--small { .conversation--actions .multiselect-wrap--small {
.multiselect { .multiselect {
@apply box-border pl-6; @apply box-border pl-6;
} }
.multiselect__element { .multiselect__element {
span { span {
@apply w-full; @apply w-full;

View File

@ -56,9 +56,9 @@ export default {
<template> <template>
<div <div
class="macro-preview absolute max-h-[22.5rem] w-64 rounded-md bg-white dark:bg-slate-800 shadow-lg bottom-8 right-8 overflow-y-auto p-4 text-left rtl:text-right" class="macro-preview absolute border border-n-weak max-h-[22.5rem] z-50 w-64 rounded-md bg-n-alpha-3 backdrop-blur-[100px] shadow-lg bottom-8 right-8 overflow-y-auto p-4 text-left rtl:text-right"
> >
<h6 class="text-sm text-slate-800 dark:text-slate-100 mb-4"> <h6 class="mb-4 text-sm text-n-slate-12">
{{ macro.name }} {{ macro.name }}
</h6> </h6>
<div <div
@ -71,12 +71,12 @@ export default {
class="top-[0.390625rem] absolute -bottom-1 left-0 w-px bg-slate-75 dark:bg-slate-600" class="top-[0.390625rem] absolute -bottom-1 left-0 w-px bg-slate-75 dark:bg-slate-600"
/> />
<div <div
class="absolute -left-[0.21875rem] top-[0.2734375rem] w-2 h-2 rounded-full bg-white dark:bg-slate-200 border-2 border-solid border-slate-100 dark:border-slate-600" class="absolute -left-[0.21875rem] top-[0.2734375rem] w-2 h-2 rounded-full bg-n-solid-1 border-2 border-solid border-n-weak dark:border-slate-600"
/> />
<p class="text-xs text-slate-500 dark:text-slate-400 mb-1"> <p class="mb-1 text-xs text-n-slate-11">
{{ action.actionName }} {{ action.actionName }}
</p> </p>
<p class="text-slate-800 dark:text-slate-100">{{ action.actionValue }}</p> <p class="text-n-slate-12 text-sm">{{ action.actionValue }}</p>
</div> </div>
</div> </div>
</template> </template>

View File

@ -10,6 +10,8 @@ import EditContact from './EditContact.vue';
import NewConversation from './NewConversation.vue'; import NewConversation from './NewConversation.vue';
import ContactMergeModal from 'dashboard/modules/contact/ContactMergeModal.vue'; import ContactMergeModal from 'dashboard/modules/contact/ContactMergeModal.vue';
import { BUS_EVENTS } from 'shared/constants/busEvents'; import { BUS_EVENTS } from 'shared/constants/busEvents';
import NextButton from 'dashboard/components-next/button/Button.vue';
import { import {
isAConversationRoute, isAConversationRoute,
isAInboxViewRoute, isAInboxViewRoute,
@ -19,6 +21,7 @@ import { emitter } from 'shared/helpers/mitt';
export default { export default {
components: { components: {
NextButton,
ContactInfoRow, ContactInfoRow,
EditContact, EditContact,
Thumbnail, Thumbnail,
@ -35,16 +38,8 @@ export default {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
showCloseButton: {
type: Boolean,
default: true,
},
closeIconName: {
type: String,
default: 'chevron-right',
},
}, },
emits: ['togglePanel', 'panelClose'], emits: ['panelClose'],
setup() { setup() {
const { isAdmin } = useAdmin(); const { isAdmin } = useAdmin();
return { return {
@ -102,9 +97,6 @@ export default {
toggleEditModal() { toggleEditModal() {
this.showEditModal = !this.showEditModal; this.showEditModal = !this.showEditModal;
}, },
onPanelToggle() {
this.$emit('togglePanel');
},
toggleConversationModal() { toggleConversationModal() {
this.showConversationModal = !this.showConversationModal; this.showConversationModal = !this.showConversationModal;
emitter.emit( emitter.emit(
@ -180,49 +172,36 @@ export default {
<Thumbnail <Thumbnail
v-if="showAvatar" v-if="showAvatar"
:src="contact.thumbnail" :src="contact.thumbnail"
size="56px" size="48px"
:username="contact.name" :username="contact.name"
:status="contact.availability_status" :status="contact.availability_status"
/> />
<woot-button
v-if="showCloseButton"
:icon="closeIconName"
class="clear secondary rtl:rotate-180"
@click="onPanelToggle"
/>
</div> </div>
<div class="flex flex-col items-start gap-1.5 min-w-0 w-full"> <div class="flex flex-col items-start gap-1.5 min-w-0 w-full">
<div v-if="showAvatar" class="flex items-start w-full min-w-0 gap-2"> <div v-if="showAvatar" class="flex items-center w-full min-w-0 gap-3">
<h3 <h3
class="flex-shrink max-w-full min-w-0 my-0 text-base capitalize break-words text-slate-800 dark:text-slate-100" class="flex-shrink max-w-full min-w-0 my-0 text-base capitalize break-words text-n-slate-12"
> >
{{ contact.name }} {{ contact.name }}
</h3> </h3>
<div class="flex flex-row items-center gap-1"> <div class="flex flex-row items-center gap-2">
<fluent-icon <span
v-if="contact.created_at" v-if="contact.created_at"
v-tooltip.left=" v-tooltip.left="
`${$t('CONTACT_PANEL.CREATED_AT_LABEL')} ${dynamicTime( `${$t('CONTACT_PANEL.CREATED_AT_LABEL')} ${dynamicTime(
contact.created_at contact.created_at
)}` )}`
" "
icon="info" class="i-lucide-info text-sm text-n-slate-10"
size="14"
class="mt-0.5"
/> />
<a <a
:href="contactProfileLink" :href="contactProfileLink"
class="text-base"
target="_blank" target="_blank"
rel="noopener nofollow noreferrer" rel="noopener nofollow noreferrer"
class="leading-3"
> >
<woot-button <span class="i-lucide-external-link text-sm text-n-slate-10" />
size="tiny"
icon="open"
variant="clear"
color-scheme="secondary"
/>
</a> </a>
</div> </div>
</div> </div>
@ -271,39 +250,39 @@ export default {
</div> </div>
</div> </div>
<div class="flex items-center w-full mt-0.5 gap-2"> <div class="flex items-center w-full mt-0.5 gap-2">
<woot-button <NextButton
v-tooltip="$t('CONTACT_PANEL.NEW_MESSAGE')" v-tooltip.top-end="$t('CONTACT_PANEL.NEW_MESSAGE')"
:title="$t('CONTACT_PANEL.NEW_MESSAGE')" icon="i-ph-chat-circle-dots"
icon="chat" slate
size="small" faded
sm
@click="toggleConversationModal" @click="toggleConversationModal"
/> />
<woot-button <NextButton
v-tooltip="$t('EDIT_CONTACT.BUTTON_LABEL')" v-tooltip.top-end="$t('EDIT_CONTACT.BUTTON_LABEL')"
:title="$t('EDIT_CONTACT.BUTTON_LABEL')" icon="i-ph-pencil-simple"
icon="edit" slate
variant="smooth" faded
size="small" sm
@click="toggleEditModal" @click="toggleEditModal"
/> />
<woot-button <NextButton
v-tooltip="$t('CONTACT_PANEL.MERGE_CONTACT')" v-tooltip.top-end="$t('CONTACT_PANEL.MERGE_CONTACT')"
:title="$t('CONTACT_PANEL.MERGE_CONTACT')" icon="i-ph-arrows-merge"
icon="merge" slate
variant="smooth" faded
size="small" sm
color-scheme="secondary"
:disabled="uiFlags.isMerging" :disabled="uiFlags.isMerging"
@click="openMergeModal" @click="openMergeModal"
/> />
<woot-button <NextButton
v-if="isAdmin" v-if="isAdmin"
v-tooltip="$t('DELETE_CONTACT.BUTTON_LABEL')" v-tooltip.top-end="$t('DELETE_CONTACT.BUTTON_LABEL')"
:title="$t('DELETE_CONTACT.BUTTON_LABEL')" icon="i-ph-trash"
icon="delete" slate
variant="smooth" faded
size="small" sm
color-scheme="alert" ruby
:disabled="uiFlags.isDeleting" :disabled="uiFlags.isDeleting"
@click="toggleDeleteModal" @click="toggleDeleteModal"
/> />

View File

@ -44,7 +44,7 @@ export default {
<a <a
v-if="href" v-if="href"
:href="href" :href="href"
class="flex items-center gap-2 text-slate-800 dark:text-slate-100 hover:underline" class="flex items-center gap-2 text-n-slate-11 hover:underline"
> >
<EmojiOrIcon <EmojiOrIcon
:icon="icon" :icon="icon"
@ -59,9 +59,9 @@ export default {
> >
{{ value }} {{ value }}
</span> </span>
<span v-else class="text-sm text-slate-300 dark:text-slate-600">{{ <span v-else class="text-sm text-n-slate-11">
$t('CONTACT_PANEL.NOT_AVAILABLE') {{ $t('CONTACT_PANEL.NOT_AVAILABLE') }}
}}</span> </span>
<woot-button <woot-button
v-if="showCopy" v-if="showCopy"
@ -75,10 +75,7 @@ export default {
/> />
</a> </a>
<div <div v-else class="flex items-center gap-2 text-n-slate-11">
v-else
class="flex items-center gap-2 text-slate-800 dark:text-slate-100"
>
<EmojiOrIcon <EmojiOrIcon
:icon="icon" :icon="icon"
:emoji="emoji" :emoji="emoji"
@ -90,9 +87,9 @@ export default {
v-dompurify-html="value" v-dompurify-html="value"
class="overflow-hidden text-sm whitespace-nowrap text-ellipsis" class="overflow-hidden text-sm whitespace-nowrap text-ellipsis"
/> />
<span v-else class="text-sm text-slate-300 dark:text-slate-600">{{ <span v-else class="text-sm text-n-slate-11">
$t('CONTACT_PANEL.NOT_AVAILABLE') {{ $t('CONTACT_PANEL.NOT_AVAILABLE') }}
}}</span> </span>
</div> </div>
</div> </div>
</template> </template>

View File

@ -166,12 +166,12 @@ onMounted(() => {
}); });
const evenClass = [ const evenClass = [
'[&>*:nth-child(odd)]:!bg-white [&>*:nth-child(even)]:!bg-slate-25', '[&>*:nth-child(odd)]:!bg-n-background [&>*:nth-child(even)]:!bg-n-slate-2',
'dark:[&>*:nth-child(odd)]:!bg-slate-900 dark:[&>*:nth-child(even)]:!bg-slate-800/50', 'dark:[&>*:nth-child(odd)]:!bg-n-background dark:[&>*:nth-child(even)]:!bg-n-solid-1',
]; ];
const oddClass = [ const oddClass = [
'[&>*:nth-child(odd)]:!bg-slate-25 [&>*:nth-child(even)]:!bg-white', '[&>*:nth-child(odd)]:!bg-n-slate-2 [&>*:nth-child(even)]:!bg-n-background',
'dark:[&>*:nth-child(odd)]:!bg-slate-800/50 dark:[&>*:nth-child(even)]:!bg-slate-900', 'dark:[&>*:nth-child(odd)]:!bg-n-solid-1 dark:[&>*:nth-child(even)]:!bg-n-background',
]; ];
const wrapperClass = computed(() => { const wrapperClass = computed(() => {
@ -195,7 +195,7 @@ const wrapperClass = computed(() => {
:attribute-regex="attribute.regex_pattern" :attribute-regex="attribute.regex_pattern"
:regex-cue="attribute.regex_cue" :regex-cue="attribute.regex_cue"
:contact-id="contactId" :contact-id="contactId"
class="border-b border-solid border-slate-50 dark:border-slate-700/50" class="border-b border-solid border-n-weak/50 dark:border-n-weak/90"
@update="onUpdate" @update="onUpdate"
@delete="onDelete" @delete="onDelete"
@copy="onCopy" @copy="onCopy"

View File

@ -68,7 +68,7 @@ useKeyboardEvents(keyboardEvents);
ref="searchInputRef" ref="searchInputRef"
type="text" type="text"
:placeholder="$t('HELP_CENTER.ARTICLE_SEARCH.PLACEHOLDER')" :placeholder="$t('HELP_CENTER.ARTICLE_SEARCH.PLACEHOLDER')"
class="block w-full !h-9 ltr:!pl-8 rtl:!pr-8 dark:!bg-slate-700 !bg-slate-25 text-sm rounded-md leading-8 text-slate-700 shadow-sm ring-2 ring-transparent ring-slate-300 border border-solid border-slate-300 placeholder:text-slate-400 focus:border-woot-600 focus:ring-woot-200 !mb-0 focus:bg-slate-25 dark:focus:bg-slate-700 dark:focus:ring-woot-700" class="block w-full !h-9 ltr:!pl-8 rtl:!pr-8 dark:!bg-n-slate-2 !border-n-weak !bg-n-slate-2 text-sm rounded-md leading-8 text-slate-700 shadow-sm ring-2 ring-transparent ring-slate-300 border border-solid placeholder:text-slate-400 focus:border-woot-600 focus:ring-woot-200 !mb-0 focus:bg-slate-25 dark:focus:bg-slate-700 dark:focus:ring-woot-700"
:value="searchQuery" :value="searchQuery"
@input="onInput" @input="onInput"
/> />

View File

@ -206,7 +206,7 @@ export default {
<template> <template>
<section class="flex w-full h-full bg-n-solid-1"> <section class="flex w-full h-full bg-n-solid-1">
<div <div
class="flex flex-col h-full w-full lg:min-w-[400px] lg:max-w-[400px] ltr:border-r rtl:border-l border-slate-50 dark:border-slate-800/50" class="flex flex-col h-full w-full lg:min-w-[400px] lg:max-w-[400px] ltr:border-r rtl:border-l border-n-weak"
:class="!currentNotificationId ? 'flex' : 'hidden xl:flex'" :class="!currentNotificationId ? 'flex' : 'hidden xl:flex'"
> >
<InboxListHeader <InboxListHeader
@ -216,7 +216,7 @@ export default {
/> />
<div <div
ref="notificationList" ref="notificationList"
class="flex flex-col gap-px w-full h-[calc(100%-56px)] pb-3 overflow-x-hidden px-3 overflow-y-auto divide-y divide-n-strong [&>*:hover]:!border-y-transparent [&>*.active]:!border-y-transparent [&>*:hover+*]:!border-t-transparent [&>*.active+*]:!border-t-transparent" class="flex flex-col gap-px w-full h-[calc(100%-56px)] pb-3 overflow-x-hidden px-3 overflow-y-auto divide-y divide-n-weak [&>*:hover]:!border-y-transparent [&>*.active]:!border-y-transparent [&>*:hover+*]:!border-t-transparent [&>*.active+*]:!border-t-transparent"
> >
<InboxCard <InboxCard
v-for="notificationItem in notificationsV4" v-for="notificationItem in notificationsV4"

View File

@ -109,7 +109,7 @@ export default {
<template> <template>
<div <div
class="flex items-center justify-between w-full gap-2 py-2 border-b ltr:pl-4 rtl:pl-2 h-14 ltr:pr-2 rtl:pr-4 rtl:border-r border-slate-50 dark:border-slate-800/50" class="flex items-center justify-between w-full gap-2 py-2 border-b ltr:pl-4 rtl:pl-2 h-14 ltr:pr-2 rtl:pr-4 rtl:border-r border-n-weak"
> >
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<BackButton <BackButton
@ -131,7 +131,7 @@ export default {
icon="i-lucide-bell-minus" icon="i-lucide-bell-minus"
slate slate
xs xs
outline faded
class="[&>.truncate]:hidden md:[&>.truncate]:block" class="[&>.truncate]:hidden md:[&>.truncate]:block"
@click="openSnoozeNotificationModal" @click="openSnoozeNotificationModal"
/> />
@ -140,7 +140,7 @@ export default {
icon="i-lucide-trash-2" icon="i-lucide-trash-2"
slate slate
xs xs
outline faded
class="[&>.truncate]:hidden md:[&>.truncate]:block" class="[&>.truncate]:hidden md:[&>.truncate]:block"
@click="deleteNotification" @click="deleteNotification"
/> />

View File

@ -79,13 +79,9 @@ export default {
</script> </script>
<template> <template>
<div <div class="flex items-center justify-between w-full gap-1 h-14 px-4 mb-2">
class="flex items-center justify-between w-full gap-1 pt-4 pb-2 h-14 ltr:pl-6 rtl:pl-3 rtl:pr-6 ltr:pr-3" <div class="flex items-center gap-2 min-w-0 flex-1">
> <h1 class="min-w-0 text-lg font-medium truncate text-n-slate-12">
<div class="flex items-center gap-1.5 min-w-0 flex-1">
<h1
class="min-w-0 text-xl font-medium truncate text-slate-900 dark:text-slate-25"
>
{{ $t('INBOX.LIST.TITLE') }} {{ $t('INBOX.LIST.TITLE') }}
</h1> </h1>
<div class="relative"> <div class="relative">

View File

@ -46,7 +46,7 @@ export default {
icon="i-lucide-chevron-up" icon="i-lucide-chevron-up"
xs xs
slate slate
outline faded
:disabled="isUpDisabled" :disabled="isUpDisabled"
@click="handleUpClick" @click="handleUpClick"
/> />
@ -54,7 +54,7 @@ export default {
icon="i-lucide-chevron-down" icon="i-lucide-chevron-down"
xs xs
slate slate
outline faded
:disabled="isDownDisabled" :disabled="isDownDisabled"
@click="handleDownClick" @click="handleDownClick"
/> />

View File

@ -20,7 +20,7 @@ export const routes = [
component: InboxEmptyStateView, component: InboxEmptyStateView,
meta: { meta: {
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS], permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
featureFlag: FEATURE_FLAGS.INBOX_VIEW, featureFlag: FEATURE_FLAGS.CHATWOOT_V4,
}, },
}, },
{ {
@ -29,7 +29,7 @@ export const routes = [
component: InboxDetailView, component: InboxDetailView,
meta: { meta: {
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS], permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
featureFlag: FEATURE_FLAGS.INBOX_VIEW, featureFlag: FEATURE_FLAGS.CHATWOOT_V4,
}, },
}, },
], ],

View File

@ -54,7 +54,7 @@ export default {
<template> <template>
<div <div
class="flex justify-between items-center h-14 min-h-[3.5rem] px-4 py-2 bg-white dark:bg-slate-900 border-b border-slate-50 dark:border-slate-800/50" class="flex justify-between items-center h-14 min-h-[3.5rem] px-4 py-2 bg-n-background border-b border-n-weak"
> >
<h1 <h1
class="flex items-center mb-0 text-2xl text-slate-900 dark:text-slate-100" class="flex items-center mb-0 text-2xl text-slate-900 dark:text-slate-100"

View File

@ -9,7 +9,7 @@ defineProps({
<template> <template>
<div <div
class="flex flex-col w-full h-full m-0 p-6 sm:py-8 lg:px-16 overflow-auto bg-white dark:bg-slate-900 font-inter" 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"
> >
<div class="flex items-start w-full max-w-6xl mx-auto"> <div class="flex items-start w-full max-w-6xl mx-auto">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">

View File

@ -23,7 +23,7 @@ const showNewButton = computed(
<template> <template>
<div <div
class="flex flex-1 h-full justify-between flex-col m-0 bg-slate-25 dark:bg-slate-900 overflow-auto" class="flex flex-1 h-full justify-between flex-col m-0 bg-n-background overflow-auto"
> >
<SettingsHeader <SettingsHeader
button-route="new" button-route="new"

View File

@ -161,9 +161,7 @@ const confirmDeletion = () => {
</template> </template>
<template #body> <template #body>
<table class="divide-y divide-slate-75 dark:divide-slate-700"> <table class="divide-y divide-slate-75 dark:divide-slate-700">
<tbody <tbody class="divide-y divide-n-weak text-n-slate-11">
class="divide-y divide-slate-50 dark:divide-slate-800 text-slate-700 dark:text-slate-300"
>
<tr v-for="(agent, index) in agentList" :key="agent.email"> <tr v-for="(agent, index) in agentList" :key="agent.email">
<td class="py-4 ltr:pr-4 rtl:pl-4"> <td class="py-4 ltr:pr-4 rtl:pl-4">
<div class="flex flex-row items-center gap-4"> <div class="flex flex-row items-center gap-4">

View File

@ -93,7 +93,7 @@ const tableHeaders = computed(() => {
<th <th
v-for="tableHeader in tableHeaders" v-for="tableHeader in tableHeaders"
:key="tableHeader" :key="tableHeader"
class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-slate-700 dark:text-slate-300" class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-n-slate-11"
> >
{{ tableHeader }} {{ tableHeader }}
</th> </th>

View File

@ -100,14 +100,12 @@ const tableHeaders = computed(() => {
<th <th
v-for="thHeader in tableHeaders" v-for="thHeader in tableHeaders"
:key="thHeader" :key="thHeader"
class="py-4 pr-4 text-left font-semibold text-slate-700 dark:text-slate-300" class="py-4 pr-4 text-left font-semibold text-n-slate-11"
> >
{{ thHeader }} {{ thHeader }}
</th> </th>
</thead> </thead>
<tbody <tbody class="divide-y divide-n-weak text-n-slate-11">
class="divide-y divide-slate-50 dark:divide-slate-800 text-slate-700 dark:text-slate-300"
>
<tr v-for="auditLogItem in records" :key="auditLogItem.id"> <tr v-for="auditLogItem in records" :key="auditLogItem.id">
<td class="py-4 pr-4 break-all whitespace-nowrap"> <td class="py-4 pr-4 break-all whitespace-nowrap">
{{ generateLogText(auditLogItem) }} {{ generateLogText(auditLogItem) }}

View File

@ -201,14 +201,12 @@ const tableHeaders = computed(() => {
<th <th
v-for="thHeader in tableHeaders" v-for="thHeader in tableHeaders"
:key="thHeader" :key="thHeader"
class="py-4 pr-4 text-left font-semibold text-slate-700 dark:text-slate-300" class="py-4 pr-4 text-left font-semibold text-n-slate-11"
> >
{{ thHeader }} {{ thHeader }}
</th> </th>
</thead> </thead>
<tbody <tbody class="divide-y divide-n-weak text-n-slate-11">
class="divide-y divide-slate-50 dark:divide-slate-800 text-slate-700 dark:text-slate-300"
>
<AutomationRuleRow <AutomationRuleRow
v-for="automation in records" v-for="automation in records"
:key="automation.id" :key="automation.id"

View File

@ -153,7 +153,7 @@ const tableHeaders = computed(() => {
<th <th
v-for="thHeader in tableHeaders" v-for="thHeader in tableHeaders"
:key="thHeader" :key="thHeader"
class="py-4 pr-4 text-left font-semibold text-slate-700 dark:text-slate-300 last:text-right" class="py-4 pr-4 text-left font-semibold text-n-slate-11 last:text-right"
> >
<span v-if="thHeader !== tableHeaders[0]"> <span v-if="thHeader !== tableHeaders[0]">
{{ thHeader }} {{ thHeader }}
@ -173,9 +173,7 @@ const tableHeaders = computed(() => {
</button> </button>
</th> </th>
</thead> </thead>
<tbody <tbody class="divide-y divide-n-weak text-n-slate-11">
class="divide-y divide-slate-50 dark:divide-slate-800 text-slate-700 dark:text-slate-300"
>
<tr <tr
v-for="(cannedItem, index) in records" v-for="(cannedItem, index) in records"
:key="cannedItem.short_code" :key="cannedItem.short_code"

View File

@ -2,6 +2,9 @@
import CustomBrandPolicyWrapper from 'dashboard/components/CustomBrandPolicyWrapper.vue'; import CustomBrandPolicyWrapper from 'dashboard/components/CustomBrandPolicyWrapper.vue';
import { getHelpUrlForFeature } from '../../../../helper/featureHelper'; import { getHelpUrlForFeature } from '../../../../helper/featureHelper';
import BackButton from '../../../../components/widgets/BackButton.vue'; import BackButton from '../../../../components/widgets/BackButton.vue';
import ButtonV4 from 'dashboard/components-next/button/Button.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
const props = defineProps({ const props = defineProps({
title: { title: {
type: String, type: String,
@ -72,7 +75,7 @@ const openInNewTab = url => {
<slot name="actions" /> <slot name="actions" />
</div> </div>
</div> </div>
<div class="flex flex-col gap-3 text-slate-600 dark:text-slate-300 w-full"> <div class="flex flex-col w-full gap-3 text-slate-600 dark:text-slate-300">
<p <p
class="mb-0 text-base font-normal line-clamp-5 sm:line-clamp-none max-w-3xl tracking-[-0.1px]" class="mb-0 text-base font-normal line-clamp-5 sm:line-clamp-none max-w-3xl tracking-[-0.1px]"
> >
@ -84,32 +87,30 @@ const openInNewTab = url => {
:href="helpURL" :href="helpURL"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="sm:inline-flex hidden gap-1 w-fit items-center text-woot-500 dark:text-woot-500 text-sm font-medium hover:underline" class="items-center hidden gap-1 text-sm font-medium sm:inline-flex w-fit text-n-brand dark:text-n-brand hover:underline"
> >
{{ linkText }} {{ linkText }}
<fluent-icon <Icon
size="16" icon="i-lucide-chevron-right"
icon="chevron-right" class="flex-shrink-0 text-n-blue-text size-4"
type="outline"
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
/> />
</a> </a>
</CustomBrandPolicyWrapper> </CustomBrandPolicyWrapper>
</div> </div>
<div <div
class="flex items-start justify-start w-full gap-3 sm:hidden flex-wrap" class="flex flex-wrap items-start justify-start w-full gap-3 sm:hidden"
> >
<slot name="actions" /> <slot name="actions" />
<CustomBrandPolicyWrapper :show-on-custom-branded-instance="false"> <CustomBrandPolicyWrapper :show-on-custom-branded-instance="false">
<woot-button <ButtonV4
v-if="helpURL && linkText" v-if="helpURL && linkText"
color-scheme="secondary" link
icon="arrow-outwards" primary
class="flex-row-reverse rounded-md min-w-0 !bg-slate-50 !text-slate-900 dark:!text-white dark:!bg-slate-800" icon="i-lucide-chevron-right"
trailing-icon
:label="linkText"
@click="openInNewTab(helpURL)" @click="openInNewTab(helpURL)"
> />
{{ linkText }}
</woot-button>
</CustomBrandPolicyWrapper> </CustomBrandPolicyWrapper>
</div> </div>
</div> </div>

View File

@ -25,9 +25,7 @@ const getFormattedPermissions = role => {
</script> </script>
<template> <template>
<tbody <tbody class="divide-y divide-n-weak text-n-slate-11">
class="divide-y divide-slate-50 dark:divide-slate-800 text-slate-700 dark:text-slate-300"
>
<tr v-for="(customRole, index) in roles" :key="index"> <tr v-for="(customRole, index) in roles" :key="index">
<td <td
class="max-w-xs py-4 pr-4 font-medium truncate align-baseline" class="max-w-xs py-4 pr-4 font-medium truncate align-baseline"

View File

@ -119,7 +119,7 @@ export default {
<th <th
v-for="thHeader in tableHeaders" v-for="thHeader in tableHeaders"
:key="thHeader" :key="thHeader"
class="py-4 pr-4 text-left font-semibold text-slate-700 dark:text-slate-300 last:text-right last:pr-4" class="py-4 pr-4 text-left font-semibold text-n-slate-11 last:text-right last:pr-4"
> >
{{ thHeader }} {{ thHeader }}
</th> </th>

View File

@ -90,14 +90,12 @@ const tableHeaders = computed(() => {
<th <th
v-for="thHeader in tableHeaders" v-for="thHeader in tableHeaders"
:key="thHeader" :key="thHeader"
class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-slate-700 dark:text-slate-300" class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-n-slate-11"
> >
{{ thHeader }} {{ thHeader }}
</th> </th>
</thead> </thead>
<tbody <tbody class="divide-y divide-n-weak text-n-slate-11">
class="divide-y divide-slate-50 dark:divide-slate-800 text-slate-700 dark:text-slate-300"
>
<MacrosTableRow <MacrosTableRow
v-for="(macro, index) in records" v-for="(macro, index) in records"
:key="index" :key="index"

View File

@ -30,7 +30,7 @@ export default {
<template> <template>
<div <div
class="p-3 bg-white dark:bg-slate-900 h-[calc(100vh-3.5rem)] flex flex-col border-l border-slate-50 dark:border-slate-800/50" class="p-3 bg-white dark:bg-slate-900 h-[calc(100vh-3.5rem)] flex flex-col border-l border-n-weak"
> >
<div> <div>
<woot-input <woot-input

View File

@ -60,7 +60,7 @@ const hasValue = computed(() => {
<woot-button <woot-button
variant="hollow" variant="hollow"
color-scheme="secondary" color-scheme="secondary"
class="w-full border border-solid border-slate-100 dark:border-slate-700 px-2 hover:border-slate-75 dark:hover:border-slate-600" class="w-full px-2 border border-solid !border-n-weak dark:!border-n-weak hover:!border-n-strong dark:hover:!border-n-strong"
@click=" @click="
() => toggleDropdown() // ensure that the event is not passed to the button () => toggleDropdown() // ensure that the event is not passed to the button
" "
@ -73,25 +73,22 @@ const hasValue = computed(() => {
:status="selectedItem.availability_status" :status="selectedItem.availability_status"
:username="selectedItem.name" :username="selectedItem.name"
/> />
<div class="flex justify-between w-full min-w-0 items-center"> <div class="flex items-center justify-between w-full min-w-0">
<h4 <h4 v-if="!hasValue" class="text-sm text-ellipsis text-n-slate-12">
v-if="!hasValue"
class="text-ellipsis text-sm text-slate-800 dark:text-slate-100"
>
{{ multiselectorPlaceholder }} {{ multiselectorPlaceholder }}
</h4> </h4>
<h4 <h4
v-else v-else
class="items-center leading-tight overflow-hidden whitespace-nowrap text-ellipsis text-sm text-slate-800 dark:text-slate-100" class="items-center overflow-hidden text-sm leading-tight whitespace-nowrap text-ellipsis text-n-slate-12"
:title="selectedItem.name" :title="selectedItem.name"
> >
{{ selectedItem.name }} {{ selectedItem.name }}
</h4> </h4>
<i <i
v-if="showSearchDropdown" v-if="showSearchDropdown"
class="icon i-lucide-chevron-up text-slate-600 mr-1" class="mr-1 icon i-lucide-chevron-up text-n-slate-10"
/> />
<i v-else class="icon i-lucide-chevron-down text-slate-600 mr-1" /> <i v-else class="mr-1 icon i-lucide-chevron-down text-n-slate-10" />
</div> </div>
</div> </div>
</woot-button> </woot-button>
@ -99,9 +96,9 @@ const hasValue = computed(() => {
:class="{ 'dropdown-pane--open': showSearchDropdown }" :class="{ 'dropdown-pane--open': showSearchDropdown }"
class="dropdown-pane" class="dropdown-pane"
> >
<div class="flex justify-between items-center mb-1"> <div class="flex items-center justify-between mb-1">
<h4 <h4
class="text-sm text-slate-800 dark:text-slate-100 m-0 overflow-hidden whitespace-nowrap text-ellipsis" class="m-0 overflow-hidden text-sm text-n-slate-11 whitespace-nowrap text-ellipsis"
> >
{{ multiselectorTitle }} {{ multiselectorTitle }}
</h4> </h4>

View File

@ -71,7 +71,7 @@ export default {
<template> <template>
<div class="dropdown-wrap"> <div class="dropdown-wrap">
<div class="mb-2 flex-shrink-0 flex-grow-0 flex-auto max-h-8"> <div class="flex-auto flex-grow-0 flex-shrink-0 mb-2 max-h-8">
<input <input
ref="searchbar" ref="searchbar"
v-model="search" v-model="search"
@ -81,7 +81,7 @@ export default {
:placeholder="inputPlaceholder" :placeholder="inputPlaceholder"
/> />
</div> </div>
<div class="flex justify-start items-start flex-auto overflow-auto"> <div class="flex items-start justify-start flex-auto overflow-auto mt-2">
<div class="w-full max-h-[10rem]"> <div class="w-full max-h-[10rem]">
<WootDropdownMenu> <WootDropdownMenu>
<WootDropdownItem v-for="option in filteredOptions" :key="option.id"> <WootDropdownItem v-for="option in filteredOptions" :key="option.id">
@ -107,7 +107,7 @@ export default {
class="flex items-center justify-between w-full min-w-0 gap-2" class="flex items-center justify-between w-full min-w-0 gap-2"
> >
<span <span
class="leading-4 my-0 overflow-hidden whitespace-nowrap text-ellipsis text-sm" class="my-0 overflow-hidden text-sm leading-4 whitespace-nowrap text-ellipsis"
:title="option.name" :title="option.name"
> >
{{ option.name }} {{ option.name }}
@ -120,7 +120,7 @@ export default {
</WootDropdownMenu> </WootDropdownMenu>
<h4 <h4
v-if="noResult" v-if="noResult"
class="w-full justify-center items-center flex text-slate-500 dark:text-slate-300 py-2 px-2.5 overflow-hidden whitespace-nowrap text-ellipsis text-sm" class="w-full justify-center items-center flex text-n-slate-10 py-2 px-2.5 overflow-hidden whitespace-nowrap text-ellipsis text-sm"
> >
{{ noSearchResult }} {{ noSearchResult }}
</h4> </h4>
@ -142,15 +142,11 @@ export default {
@apply justify-between w-full; @apply justify-between w-full;
&.active { &.active {
@apply bg-slate-25 dark:bg-slate-700 border-slate-50 dark:border-slate-900 font-medium; @apply bg-n-slate-2 dark:bg-n-solid-3 border-n-weak/50 dark:border-n-weak font-medium;
}
&:focus {
@apply bg-slate-25 dark:bg-slate-700;
} }
&:hover { &:hover {
@apply bg-slate-50 dark:bg-slate-800 text-slate-800 dark:text-slate-100; @apply bg-n-slate-2 dark:bg-n-solid-3 text-slate-800 dark:text-slate-100;
} }
} }
</style> </style>

View File

@ -24,7 +24,7 @@ export default {
methods: { methods: {
addEventHandler(keydownHandler) { addEventHandler(keydownHandler) {
const indexToAppend = taggedHandlers.push(keydownHandler) - 1; const indexToAppend = taggedHandlers.push(keydownHandler) - 1;
const root = this.$el; const root = this.getElementToBind();
if (root && root.dataset) { if (root && root.dataset) {
// For the components with a top level v-if Vue renders it as an empty comment in the DOM // For the components with a top level v-if Vue renders it as an empty comment in the DOM
// so we need to check if the root element has a dataset property to ensure it is a valid element // so we need to check if the root element has a dataset property to ensure it is a valid element
@ -32,6 +32,9 @@ export default {
root.dataset.keydownHandlerIndex = indexToAppend; root.dataset.keydownHandlerIndex = indexToAppend;
} }
}, },
getElementToBind() {
return this.$el;
},
getKeyboardEvents() { getKeyboardEvents() {
return null; return null;
}, },