feat: Update settings pages UI (#13191)
This commit is contained in:
@@ -22,11 +22,11 @@ const handleButtonClick = () => {
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-surface-1">
|
||||
<header
|
||||
class="sticky top-0 z-10 px-6 lg:px-0 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none"
|
||||
class="sticky top-0 z-10 px-6 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none"
|
||||
>
|
||||
<div class="w-full max-w-5xl mx-auto lg:px-6">
|
||||
<div class="w-full max-w-5xl mx-auto">
|
||||
<div class="flex items-center justify-between w-full h-20 gap-2">
|
||||
<span class="text-xl font-medium text-n-slate-12">
|
||||
<span class="text-heading-1 text-n-slate-12">
|
||||
{{ headerTitle }}
|
||||
</span>
|
||||
<div
|
||||
@@ -45,8 +45,8 @@ const handleButtonClick = () => {
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main class="flex-1 px-6 overflow-y-auto lg:px-0">
|
||||
<div class="w-full max-w-5xl mx-auto py-4 lg:px-6">
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-5xl py-4">
|
||||
<slot name="default" />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
+3
-3
@@ -16,12 +16,12 @@ const emit = defineEmits(['search', 'update:sort']);
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="sticky top-0 z-10 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none"
|
||||
class="sticky top-0 z-10 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none px-6"
|
||||
>
|
||||
<div
|
||||
class="flex items-start sm:items-center justify-between w-full py-6 px-6 gap-2 mx-auto max-w-[60rem]"
|
||||
class="flex items-start sm:items-center justify-between w-full py-6 gap-2 mx-auto max-w-5xl"
|
||||
>
|
||||
<span class="text-xl font-medium truncate text-n-slate-12">
|
||||
<span class="text-heading-1 truncate text-n-slate-12">
|
||||
{{ headerTitle }}
|
||||
</span>
|
||||
<div class="flex items-center flex-row flex-shrink-0 gap-2">
|
||||
|
||||
@@ -32,8 +32,8 @@ const updateCurrentPage = page => {
|
||||
@search="emit('search', $event)"
|
||||
@update:sort="emit('update:sort', $event)"
|
||||
/>
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem]">
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-5xl py-4">
|
||||
<slot name="default" />
|
||||
</div>
|
||||
</main>
|
||||
@@ -43,7 +43,7 @@ const updateCurrentPage = page => {
|
||||
:current-page="currentPage"
|
||||
:total-items="totalItems"
|
||||
:items-per-page="25"
|
||||
class="max-w-[105rem]"
|
||||
class="max-w-[67rem]"
|
||||
@update:current-page="updateCurrentPage"
|
||||
/>
|
||||
</footer>
|
||||
|
||||
@@ -36,7 +36,7 @@ const emit = defineEmits([
|
||||
<div
|
||||
class="flex items-start sm:items-center justify-between w-full py-6 px-6 gap-2 mx-auto max-w-[105rem] after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none"
|
||||
>
|
||||
<span class="text-lg leading-6 font-520 truncate text-n-slate-12">
|
||||
<span class="text-heading-1 truncate text-n-slate-12">
|
||||
{{ headerTitle }}
|
||||
</span>
|
||||
<div class="flex items-center flex-col sm:flex-row flex-shrink-0 gap-4">
|
||||
|
||||
@@ -86,7 +86,7 @@ const openFilter = () => {
|
||||
@clear-filters="emit('clearFilters')"
|
||||
/>
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[105rem]">
|
||||
<div class="w-full mx-auto">
|
||||
<ContactsActiveFiltersPreview
|
||||
v-if="showActiveFiltersPreview"
|
||||
:active-segment="activeSegment"
|
||||
@@ -102,7 +102,6 @@ const openFilter = () => {
|
||||
:current-page="currentPage"
|
||||
:total-items="totalItems"
|
||||
:items-per-page="itemsPerPage"
|
||||
class="max-w-[105rem]"
|
||||
@update:current-page="updateCurrentPage"
|
||||
/>
|
||||
</footer>
|
||||
|
||||
+2
-2
@@ -145,12 +145,12 @@ const selectedModel = computed({
|
||||
</div>
|
||||
|
||||
<!-- RIGHT SECTION -->
|
||||
<div class="flex items-center justify-end gap-2 flex-shrink-0">
|
||||
<div class="flex items-center justify-end gap-1.5 flex-shrink-0">
|
||||
<div v-if="showLabelsSection" class="min-w-0 w-full">
|
||||
<CardLabels
|
||||
:labels="chat.labels"
|
||||
disable-toggle
|
||||
class="my-0 [&>div]:justify-end"
|
||||
class="my-0 [&>div]:justify-end justify-end"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ defineProps({
|
||||
<template>
|
||||
<div :title="inbox.name" class="flex items-center gap-0.5 min-w-0">
|
||||
<ChannelIcon :inbox="inbox" class="size-4 flex-shrink-0 text-n-slate-11" />
|
||||
<span class="truncate text-label-small text-n-slate-12">
|
||||
<span class="truncate text-label-small text-n-slate-11">
|
||||
{{ inbox.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
+46
-45
@@ -1,8 +1,10 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import Label from 'dashboard/components-next/label/Label.vue';
|
||||
import AttributeBadge from 'dashboard/components-next/CustomAttributes/AttributeBadge.vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
attribute: {
|
||||
@@ -18,7 +20,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['edit', 'delete']);
|
||||
|
||||
const iconByType = {
|
||||
text: 'i-lucide-align-justify',
|
||||
text: 'i-lucide-menu',
|
||||
checkbox: 'i-lucide-circle-check-big',
|
||||
list: 'i-lucide-list',
|
||||
date: 'i-lucide-calendar',
|
||||
@@ -28,61 +30,60 @@ const iconByType = {
|
||||
|
||||
const attributeIcon = computed(() => {
|
||||
const typeKey = props.attribute.type?.toLowerCase();
|
||||
return iconByType[typeKey] || 'i-lucide-align-justify';
|
||||
return iconByType[typeKey] || 'i-lucide-menu';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-2 p-4 bg-n-solid-1 rounded-2xl outline outline-1 outline-n-container"
|
||||
>
|
||||
<div class="flex flex-wrap gap-2 justify-between items-center">
|
||||
<div class="flex flex-wrap gap-2 items-center min-w-0">
|
||||
<h4 class="text-sm font-medium truncate text-n-slate-12">
|
||||
{{ attribute.label }}
|
||||
</h4>
|
||||
<div class="w-px h-3 bg-n-strong" />
|
||||
<div class="flex gap-2 items-center text-sm text-n-slate-11">
|
||||
<div class="flex items-center gap-1.5 text-n-slate-11">
|
||||
<Icon :icon="attributeIcon" class="size-4" />
|
||||
<span class="text-sm">{{ attribute.type }}</span>
|
||||
<div class="flex flex-col py-4 divide-y divide-n-weak">
|
||||
<div class="flex justify-between flex-row items-center gap-4 min-w-0">
|
||||
<div class="flex items-center gap-4 min-w-0">
|
||||
<div
|
||||
class="flex items-center flex-shrink-0 size-10 justify-center rounded-xl outline outline-1 outline-n-weak -outline-offset-1"
|
||||
>
|
||||
<Icon :icon="attributeIcon" class="size-4 text-n-slate-11" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5 items-start min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<h4 class="text-heading-3 truncate text-n-slate-12">
|
||||
{{ attribute.label }}
|
||||
</h4>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Label :label="attribute.type" compact />
|
||||
<AttributeBadge
|
||||
v-for="badge in badges"
|
||||
:key="badge.type"
|
||||
:type="badge.type"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-px h-3 bg-n-weak" />
|
||||
<div class="flex items-center gap-1.5 text-n-slate-11">
|
||||
<Icon icon="i-lucide-key-round" class="size-4" />
|
||||
<span class="line-clamp-1 text-sm">{{ attribute.value }}</span>
|
||||
<div class="grid grid-cols-[auto_1fr] items-center gap-1.5">
|
||||
<Icon icon="i-lucide-key-round" class="size-3.5 text-n-slate-11" />
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<span class="text-body-main text-n-slate-11 truncate">
|
||||
{{ attribute.value }}
|
||||
</span>
|
||||
<template
|
||||
v-if="attribute.attribute_description || attribute.description"
|
||||
>
|
||||
<div class="w-px h-3 rounded-lg bg-n-weak flex-shrink-0" />
|
||||
<span class="text-body-main text-n-slate-11 truncate">
|
||||
{{ attribute.attribute_description || attribute.description }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<AttributeBadge
|
||||
v-for="badge in badges"
|
||||
:key="badge.type"
|
||||
:type="badge.type"
|
||||
/>
|
||||
<div
|
||||
v-if="badges.length > 0"
|
||||
class="w-px h-3 bg-n-strong ltr:ml-1.5 rtl:mr-1.5"
|
||||
/>
|
||||
<div class="flex gap-3 justify-end flex-shrink-0">
|
||||
<Button
|
||||
icon="i-lucide-pencil-line"
|
||||
size="sm"
|
||||
color="slate"
|
||||
ghost
|
||||
icon="i-woot-edit-pen"
|
||||
slate
|
||||
sm
|
||||
@click="emit('edit', attribute)"
|
||||
/>
|
||||
<div class="w-px h-3 bg-n-strong" />
|
||||
<Button
|
||||
icon="i-lucide-trash"
|
||||
size="sm"
|
||||
color="slate"
|
||||
ghost
|
||||
@click="emit('delete', attribute)"
|
||||
/>
|
||||
<Button icon="i-woot-bin" slate sm @click="emit('delete', attribute)" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-0 text-sm text-n-slate-11">
|
||||
{{ attribute.attribute_description || attribute.description || '' }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import Label from 'dashboard/components-next/label/Label.vue';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
@@ -18,11 +19,13 @@ const attributeConfig = {
|
||||
colorClass: 'text-n-blue-11',
|
||||
icon: 'i-lucide-message-circle',
|
||||
labelKey: 'ATTRIBUTES_MGMT.BADGES.PRE_CHAT',
|
||||
color: 'slate',
|
||||
},
|
||||
resolution: {
|
||||
colorClass: 'text-n-teal-11',
|
||||
icon: 'i-lucide-circle-check-big',
|
||||
labelKey: 'ATTRIBUTES_MGMT.BADGES.RESOLUTION',
|
||||
color: 'slate',
|
||||
},
|
||||
};
|
||||
const config = computed(
|
||||
@@ -31,12 +34,9 @@ const config = computed(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex gap-1 justify-center items-center px-1.5 py-1 rounded-md shadow outline-1 outline outline-n-container bg-n-solid-2"
|
||||
>
|
||||
<Icon :icon="config.icon" class="size-4" :class="config.colorClass" />
|
||||
<span class="text-xs" :class="config.colorClass">{{
|
||||
t(config.labelKey)
|
||||
}}</span>
|
||||
</div>
|
||||
<Label :label="t(config.labelKey)" :color="config.color" compact>
|
||||
<template #icon>
|
||||
<Icon :icon="config.icon" class="size-3.5 text-n-slate-12" />
|
||||
</template>
|
||||
</Label>
|
||||
</template>
|
||||
|
||||
@@ -26,7 +26,7 @@ defineProps({
|
||||
class="relative flex flex-col items-center justify-center w-full h-full overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="relative w-full max-w-[60rem] mx-auto overflow-hidden h-full max-h-[28rem]"
|
||||
class="relative w-full max-w-5xl mx-auto overflow-hidden h-full max-h-[28rem]"
|
||||
>
|
||||
<div
|
||||
v-if="showBackdrop"
|
||||
|
||||
@@ -73,9 +73,9 @@ const onClickCreateButton = () => {
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-surface-1">
|
||||
<header
|
||||
class="sticky top-0 z-10 px-6 pb-3 lg:px-0 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none"
|
||||
class="sticky top-0 z-10 px-6 pb-3 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none"
|
||||
>
|
||||
<div class="w-full max-w-5xl mx-auto lg:px-6">
|
||||
<div class="w-full max-w-5xl mx-auto">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div
|
||||
v-if="showHeaderTitle"
|
||||
@@ -83,7 +83,7 @@ const onClickCreateButton = () => {
|
||||
>
|
||||
<span
|
||||
v-if="activePortalName"
|
||||
class="text-lg font-520 text-n-slate-12 ltr:mr-1 rtl:ml-1"
|
||||
class="text-heading-1 text-n-slate-12 ltr:mr-1 rtl:ml-1"
|
||||
>
|
||||
{{ activePortalName }}
|
||||
</span>
|
||||
@@ -91,7 +91,7 @@ const onClickCreateButton = () => {
|
||||
<OnClickOutside @trigger="showPortalSwitcher = false">
|
||||
<Button
|
||||
icon="i-lucide-chevron-down"
|
||||
variant="ghost"
|
||||
:variant="showPortalSwitcher ? 'faded' : 'ghost'"
|
||||
color="slate"
|
||||
size="xs"
|
||||
class="rounded-md group-hover:bg-n-slate-3 hover:bg-n-slate-3 [&>span]:size-4"
|
||||
@@ -133,8 +133,8 @@ const onClickCreateButton = () => {
|
||||
<slot name="header-actions" />
|
||||
</div>
|
||||
</header>
|
||||
<main class="flex-1 px-6 overflow-y-auto lg:px-0">
|
||||
<div class="w-full max-w-5xl mx-auto py-3 lg:px-6">
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full max-w-5xl mx-auto py-3">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</main>
|
||||
@@ -143,7 +143,7 @@ const onClickCreateButton = () => {
|
||||
:current-page="currentPage"
|
||||
:total-items="totalItems"
|
||||
:items-per-page="itemsPerPage"
|
||||
class="max-w-[105rem]"
|
||||
class="max-w-[67rem]"
|
||||
@update:current-page="updateCurrentPage"
|
||||
/>
|
||||
</footer>
|
||||
|
||||
@@ -23,7 +23,7 @@ const handleButtonClick = button => {
|
||||
class="flex flex-col items-start gap-3 justify-between ltr:pl-4 ltr:pr-2 rtl:pl-2 rtl:pr-4 my-2 pt-2"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2 flex-1 w-full">
|
||||
<span class="text-heading-1 text-n-slate-12">{{ title }}</span>
|
||||
<span class="text-heading-2 text-n-slate-12">{{ title }}</span>
|
||||
<div class="flex items-center h-6">
|
||||
<Button
|
||||
v-for="button in buttons"
|
||||
|
||||
@@ -121,7 +121,8 @@ const borderRadiusClass = computed(() => {
|
||||
if (props.size <= 16) return 'rounded'; // 4px
|
||||
if (props.size <= 24) return 'rounded-md'; // 6px
|
||||
if (props.size <= 32) return 'rounded-lg'; // 8px
|
||||
return 'rounded-lg'; // 16px
|
||||
if (props.size <= 48) return 'rounded-xl'; // 12px
|
||||
return 'rounded-2xl'; // 16px
|
||||
});
|
||||
|
||||
const avatarStyles = computed(() => ({
|
||||
|
||||
@@ -117,9 +117,9 @@ const handleCreateAssistant = () => {
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full overflow-hidden bg-n-surface-1">
|
||||
<header
|
||||
class="sticky top-0 z-10 px-6 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none after:z-10"
|
||||
class="sticky top-0 z-10 px-6 after:absolute after:inset-x-0 after:-bottom-4 after:bg-gradient-to-b after:from-n-surface-1 after:from-10% after:dark:from-0% after:to-transparent after:h-4 after:pointer-events-none"
|
||||
>
|
||||
<div class="w-full max-w-[60rem] mx-auto">
|
||||
<div class="w-full max-w-5xl mx-auto">
|
||||
<div
|
||||
class="flex items-start lg:items-center justify-between w-full py-6 lg:py-0 lg:h-20 gap-4 lg:gap-2 flex-col lg:flex-row"
|
||||
>
|
||||
@@ -142,7 +142,9 @@ const handleCreateAssistant = () => {
|
||||
>
|
||||
<Button
|
||||
icon="i-lucide-chevron-down"
|
||||
variant="ghost"
|
||||
:variant="
|
||||
showAssistantSwitcherDropdown ? 'faded' : 'ghost'
|
||||
"
|
||||
color="slate"
|
||||
size="xs"
|
||||
:disabled="isFetchingAssistants"
|
||||
@@ -211,7 +213,7 @@ const handleCreateAssistant = () => {
|
||||
</div>
|
||||
</header>
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full max-w-[60rem] h-full mx-auto py-4">
|
||||
<div class="w-full max-w-5xl h-full mx-auto py-4">
|
||||
<slot v-if="!showPaywall" name="controls" />
|
||||
<div
|
||||
v-if="isFetching"
|
||||
@@ -234,7 +236,7 @@ const handleCreateAssistant = () => {
|
||||
:current-page="currentPage"
|
||||
:total-items="totalCount"
|
||||
:items-per-page="itemsPerPage"
|
||||
class="max-w-[105rem]"
|
||||
class="max-w-[67rem]"
|
||||
@update:current-page="handlePageChange"
|
||||
/>
|
||||
</footer>
|
||||
|
||||
@@ -27,7 +27,7 @@ const openBilling = () => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="w-full max-w-[60rem] mx-auto h-full max-h-[448px] grid place-content-center"
|
||||
class="w-full max-w-5xl mx-auto h-full max-h-[448px] grid place-content-center"
|
||||
>
|
||||
<BasePaywallModal
|
||||
class="mx-auto"
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ const openLink = link => {
|
||||
]"
|
||||
>
|
||||
<section
|
||||
class="absolute top-full mt-6 ltr:left-0 rtl:right-0 outline outline-1 outline-n-weak bg-n-alpha-3 backdrop-blur-[100px] rounded-xl p-4 w-80"
|
||||
class="absolute top-full mt-6 ltr:left-0 rtl:right-0 outline outline-1 outline-n-weak bg-n-alpha-3 backdrop-blur-[100px] rounded-xl p-4 w-80 z-20"
|
||||
>
|
||||
<div
|
||||
class="absolute -top-[0.77rem] ltr:left-12 rtl:right-12 w-6 h-6 ltr:rotate-45 rtl:-rotate-45 rtl:rounded-tr ltr:rounded-tl rtl:border-r ltr:border-l border-t border-n-weak bg-n-alpha-3 z-10"
|
||||
|
||||
@@ -1,32 +1,70 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: Object,
|
||||
type: [Object, String],
|
||||
required: true,
|
||||
},
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'slate',
|
||||
validator: value =>
|
||||
['slate', 'amber', 'teal', 'ruby', 'blue', 'iris'].includes(value),
|
||||
},
|
||||
});
|
||||
|
||||
const COLOR_CLASSES = {
|
||||
slate: 'bg-n-label-color outline-n-label-border text-n-slate-12',
|
||||
amber: 'bg-n-amber-2 outline-n-amber-4 text-n-amber-11',
|
||||
teal: 'bg-n-teal-2 outline-n-teal-4 text-n-teal-11',
|
||||
ruby: 'bg-n-ruby-2 outline-n-ruby-4 text-n-ruby-11',
|
||||
blue: 'bg-n-blue-2 outline-n-blue-4 text-n-blue-11',
|
||||
iris: 'bg-n-iris-2 outline-n-iris-4 text-n-iris-11',
|
||||
};
|
||||
|
||||
const isStringLabel = computed(() => typeof props.label === 'string');
|
||||
|
||||
const labelTitle = computed(() => {
|
||||
return isStringLabel.value ? props.label : props.label?.title;
|
||||
});
|
||||
|
||||
const labelDescription = computed(() => {
|
||||
return (!isStringLabel.value && props.label?.description) || '';
|
||||
});
|
||||
|
||||
const labelColor = computed(() => {
|
||||
return isStringLabel.value ? null : props.label.color;
|
||||
});
|
||||
|
||||
const colorClasses = computed(() => COLOR_CLASSES[props.color]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:title="label.description"
|
||||
class="bg-n-button-color gap-1.5 rounded-lg -outline-offset-1 outline outline-1 outline-n-container inline-flex items-center flex-shrink-0"
|
||||
:class="compact ? 'px-1.5 h-6 gap-1 rounded-md' : 'px-2.5 h-8 rounded-lg'"
|
||||
:title="labelDescription"
|
||||
class="gap-1.5 rounded-lg -outline-offset-1 outline outline-1 inline-flex items-center flex-shrink-0"
|
||||
:class="[
|
||||
colorClasses,
|
||||
compact ? 'px-1.5 h-6 gap-1 rounded-md' : 'px-2.5 h-8 rounded-lg',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
v-if="labelColor"
|
||||
class="rounded-sm flex-shrink-0"
|
||||
:class="compact ? 'size-1.5' : 'size-2'"
|
||||
:style="{ background: label.color }"
|
||||
:style="{ background: labelColor }"
|
||||
/>
|
||||
<slot v-else name="icon" />
|
||||
<span
|
||||
class="text-n-slate-12 whitespace-nowrap"
|
||||
class="whitespace-nowrap"
|
||||
:class="compact ? 'text-label-small' : 'text-label !font-420'"
|
||||
>
|
||||
{{ label.title }}
|
||||
{{ labelTitle }}
|
||||
</span>
|
||||
<slot name="action" />
|
||||
</div>
|
||||
|
||||
@@ -81,7 +81,7 @@ const showDivider = index => {
|
||||
<template v-for="(tab, index) in tabs" :key="index">
|
||||
<button
|
||||
:ref="el => (tabRefs[index] = el)"
|
||||
class="relative z-10 px-4 truncate py-1.5 font-460 text-sm border-0 outline-1 outline-transparent rounded-lg transition-all duration-200 ease-out hover:text-n-brand active:scale-[1.02]"
|
||||
class="relative z-10 px-4 truncate py-1.5 text-label border-0 outline-1 outline-transparent rounded-lg transition-all duration-200 ease-out hover:text-n-brand active:scale-[1.02]"
|
||||
:class="[
|
||||
activeTab === index
|
||||
? 'text-n-blue-11 scale-100'
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { BaseTable, BaseTableRow, BaseTableCell } from './index';
|
||||
import Button from '../button/Button.vue';
|
||||
import Avatar from '../avatar/Avatar.vue';
|
||||
import ToggleSwitch from '../switch/Switch.vue';
|
||||
|
||||
const automationData = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Welcome Message',
|
||||
description: 'Send welcome message to new contacts',
|
||||
active: true,
|
||||
createdOn: 'Apr 21, 2022',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Auto-assign to Sales',
|
||||
description: 'Automatically assign sales conversations to sales team',
|
||||
active: false,
|
||||
createdOn: 'May 15, 2022',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Tag Premium Users',
|
||||
description: 'Add premium tag to conversations from premium users',
|
||||
active: true,
|
||||
createdOn: 'Jun 10, 2022',
|
||||
},
|
||||
]);
|
||||
|
||||
const agentData = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
role: 'Administrator',
|
||||
verified: true,
|
||||
avatarUrl: '',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Jane Smith',
|
||||
email: 'jane@example.com',
|
||||
role: 'Agent',
|
||||
verified: true,
|
||||
avatarUrl: '',
|
||||
},
|
||||
]);
|
||||
|
||||
const emptyData = ref([]);
|
||||
|
||||
const headers = ['Name', 'Active', 'Created on', 'Actions'];
|
||||
const agentHeaders = ['Agent', 'Role', 'Verification', 'Actions'];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story title="Components/Table" :layout="{ type: 'grid', width: '100%' }">
|
||||
<!-- Basic Table -->
|
||||
<Variant title="Basic Table">
|
||||
<div class="p-4 bg-n-surface-1">
|
||||
<BaseTable :headers="headers" :items="automationData">
|
||||
<template #row="{ items }">
|
||||
<BaseTableRow
|
||||
v-for="automation in items"
|
||||
:key="automation.id"
|
||||
:item="automation"
|
||||
>
|
||||
<template #default>
|
||||
<BaseTableCell>
|
||||
<div class="flex items-center gap-2 min-w-0 max-w-full">
|
||||
<span
|
||||
class="text-body-main text-n-slate-12 truncate min-w-0 flex-1"
|
||||
>
|
||||
{{ automation.name }}
|
||||
</span>
|
||||
<div class="w-px h-3 rounded-lg bg-n-weak flex-shrink-0" />
|
||||
<span
|
||||
class="text-body-main text-n-slate-11 truncate min-w-0 flex-1"
|
||||
>
|
||||
{{ automation.description }}
|
||||
</span>
|
||||
</div>
|
||||
</BaseTableCell>
|
||||
|
||||
<BaseTableCell>
|
||||
<div class="flex justify-center">
|
||||
<ToggleSwitch v-model="automation.active" />
|
||||
</div>
|
||||
</BaseTableCell>
|
||||
|
||||
<BaseTableCell>
|
||||
<span
|
||||
class="text-body-main text-n-slate-12 whitespace-nowrap"
|
||||
>
|
||||
{{ automation.createdOn }}
|
||||
</span>
|
||||
</BaseTableCell>
|
||||
|
||||
<BaseTableCell align="end" class="w-24">
|
||||
<div class="flex gap-3 justify-end flex-shrink-0">
|
||||
<Button icon="i-woot-edit-pen" slate sm />
|
||||
<Button icon="i-woot-bin" slate sm />
|
||||
<Button icon="i-woot-clone" slate sm />
|
||||
</div>
|
||||
</BaseTableCell>
|
||||
</template>
|
||||
</BaseTableRow>
|
||||
</template>
|
||||
</BaseTable>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<!-- Table with Avatars -->
|
||||
<Variant title="Table with Avatars">
|
||||
<div class="p-4 bg-n-surface-1">
|
||||
<BaseTable :headers="agentHeaders" :items="agentData">
|
||||
<template #row="{ items }">
|
||||
<BaseTableRow v-for="agent in items" :key="agent.id" :item="agent">
|
||||
<template #default>
|
||||
<BaseTableCell>
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<Avatar :user="agent" :size="40" class="flex-shrink-0" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<span class="text-body-main text-n-slate-12 truncate">
|
||||
{{ agent.name }}
|
||||
</span>
|
||||
<span class="text-body-main text-n-slate-11 truncate">
|
||||
{{ agent.email }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</BaseTableCell>
|
||||
|
||||
<BaseTableCell>
|
||||
<span
|
||||
class="text-body-main text-n-slate-12 whitespace-nowrap"
|
||||
>
|
||||
{{ agent.role }}
|
||||
</span>
|
||||
</BaseTableCell>
|
||||
|
||||
<BaseTableCell>
|
||||
<span
|
||||
class="text-body-main text-n-slate-12 whitespace-nowrap"
|
||||
>
|
||||
{{ agent.verified ? 'Verified' : 'Pending' }}
|
||||
</span>
|
||||
</BaseTableCell>
|
||||
|
||||
<BaseTableCell align="end" class="w-24">
|
||||
<div class="flex gap-3 justify-end flex-shrink-0">
|
||||
<Button icon="i-woot-edit-pen" slate sm />
|
||||
<Button icon="i-woot-bin" slate sm />
|
||||
</div>
|
||||
</BaseTableCell>
|
||||
</template>
|
||||
</BaseTableRow>
|
||||
</template>
|
||||
</BaseTable>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<!-- Empty State -->
|
||||
<Variant title="Empty State">
|
||||
<div class="p-4 bg-n-surface-1">
|
||||
<BaseTable
|
||||
:headers="headers"
|
||||
:items="emptyData"
|
||||
no-data-message="No automation rules found"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
headers: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
noDataMessage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const hasHeaderSlot = computed(() => !!props.headers.length);
|
||||
const showHeaders = computed(
|
||||
() => hasHeaderSlot.value && props.items.length > 0
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<table class="min-w-full table-auto divide-y divide-n-weak">
|
||||
<thead v-if="showHeaders">
|
||||
<tr>
|
||||
<th
|
||||
v-for="(header, index) in headers"
|
||||
:key="index"
|
||||
class="py-4 ltr:pr-4 rtl:pl-4 text-start text-heading-3 text-n-slate-12 capitalize"
|
||||
>
|
||||
<slot :name="`header-${index}`" :header="header">
|
||||
{{ header }}
|
||||
</slot>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-n-weak text-n-slate-11">
|
||||
<template v-if="items.length">
|
||||
<slot name="row" :items="items" />
|
||||
</template>
|
||||
<tr v-else-if="noDataMessage && !loading">
|
||||
<td
|
||||
:colspan="headers.length || 1"
|
||||
class="py-20 text-center text-body-main !text-base text-n-slate-11"
|
||||
>
|
||||
{{ noDataMessage }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
align: {
|
||||
type: String,
|
||||
default: 'start',
|
||||
validator: value => ['start', 'center', 'end'].includes(value),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td
|
||||
class="py-3 ltr:pr-4 rtl:pl-4 text-body-main"
|
||||
:class="{
|
||||
'text-start': align === 'start',
|
||||
'text-center': align === 'center',
|
||||
'text-end': align === 'end',
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</td>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr>
|
||||
<slot :item="item" />
|
||||
</tr>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as BaseTable } from './BaseTable.vue';
|
||||
export { default as BaseTableRow } from './BaseTableRow.vue';
|
||||
export { default as BaseTableCell } from './BaseTableCell.vue';
|
||||
@@ -18,7 +18,7 @@ const props = defineProps({
|
||||
},
|
||||
year: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user