feat: Creates components for Article Search in Reply [CW-2285] (#7957)

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
Nithin David Thomas
2023-10-16 15:02:02 +05:30
committed by GitHub
parent f77db4d814
commit b28721e10b
9 changed files with 309 additions and 72 deletions

View File

@@ -1,31 +1,35 @@
<template>
<div class="article-item">
<h4 class="text-block-title margin-bottom-0">{{ title }}</h4>
<p class="margin-bottom-0 overflow-hidden whitespace-nowrap text-ellipsis">
{{ body }}
</p>
<div class="footer">
<p class="text-small meta">
<div
class="flex flex-col gap-1 bg-white dark:bg-slate-900 hover:bg-slate-25 hover:dark:bg-slate-800 rounded-md py-1 px-2 w-full group"
>
<button @click="handlePreview">
<h4
class="text-block-title text-left mb-0 text-slate-900 dark:text-slate-25 px-1 -mx-1 rounded-sm hover:underline cursor-pointer width-auto"
>
{{ title }}
</h4>
</button>
<div class="flex content-between items-center gap-0.5 w-full">
<p class="text-sm text-slate-600 dark:text-slate-300 mb-0 w-full">
{{ locale }}
{{ ` / ` }}
{{
category ||
$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.ARTICLE_SEARCH_RESULT')
}}
{{ category || $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.UNCATEGORIZED') }}
</p>
<div class="action-buttons">
<div class="flex gap-0.5">
<woot-button
:title="$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.COPY_LINK')"
variant="hollow"
color-scheme="secondary"
size="tiny"
icon="copy"
class="invisible group-hover:visible"
@click="handleCopy"
/>
<a
:href="url"
class="button hollow button--only-icon tiny secondary"
class="button hollow button--only-icon tiny secondary invisible group-hover:visible"
rel="noopener noreferrer nofollow"
target="_blank"
:title="$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.OPEN_LINK')"
@@ -38,6 +42,7 @@
color-scheme="secondary"
size="tiny"
icon="preview-link"
class="invisible group-hover:visible"
:title="$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.PREVIEW_LINK')"
@click="handlePreview"
/>
@@ -46,7 +51,7 @@
variant="smooth"
color-scheme="secondary"
size="tiny"
@click="handleClick"
@click="handleInsert"
>
{{ $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.INSERT_ARTICLE') }}
</woot-button>
@@ -61,6 +66,10 @@ import { copyTextToClipboard } from 'shared/helpers/clipboard';
export default {
name: 'ArticleSearchResultItem',
props: {
id: {
type: Number,
default: 0,
},
title: {
type: String,
default: 'Untitled',
@@ -79,15 +88,15 @@ export default {
},
locale: {
type: String,
default: 'en-US',
default: '',
},
},
methods: {
handleClick() {
this.$emit('click');
handleInsert() {
this.$emit('insert', this.id);
},
handlePreview() {
this.$emit('preview');
this.$emit('preview', this.id);
},
async handleCopy() {
await copyTextToClipboard(this.url);
@@ -96,37 +105,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.article-item {
display: flex;
flex-direction: column;
gap: var(--space-micro);
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: var(--space-micro);
}
.meta {
color: var(--s-600);
margin-bottom: 0;
}
.action-buttons {
display: flex;
gap: var(--space-micro);
}
.action-buttons .button:not(.insert-button) {
visibility: hidden;
opacity: 0;
transition: all 0.1s ease-in;
}
.article-item:hover .action-buttons .button:not(.insert-button) {
visibility: visible;
opacity: 1;
}
</style>

View File

@@ -0,0 +1,63 @@
<template>
<div class="h-full w-full flex flex-col flex-1 overflow-hidden">
<div class="py-1">
<woot-button
variant="link"
size="small"
icon="chevron-left"
@click="onBack"
>
{{ $t('HELP_CENTER.ARTICLE_SEARCH.BACK_RESULTS') }}
</woot-button>
</div>
<div class="w-full h-full overflow-auto min-h-0">
<iframe-loader :url="url" />
</div>
<div class="flex justify-end gap-2 py-2">
<woot-button
variant="hollow"
size="small"
is-expanded
color-scheme="secondary"
icon="chevron-left"
@click="onBack"
>
{{ $t('HELP_CENTER.ARTICLE_SEARCH.BACK') }}
</woot-button>
<woot-button
size="small"
is-expanded
icon="arrow-download"
@click="onInsert"
>
{{ $t('HELP_CENTER.ARTICLE_SEARCH.INSERT_ARTICLE') }}
</woot-button>
</div>
</div>
</template>
<script>
import IframeLoader from 'shared/components/IframeLoader.vue';
export default {
name: 'ChatwootSearch',
components: {
IframeLoader,
},
props: {
url: {
type: String,
default: '',
},
},
methods: {
onBack() {
this.$emit('back');
},
onInsert() {
this.$emit('insert');
},
},
};
</script>

View File

@@ -0,0 +1,54 @@
<template>
<div class="flex flex-col py-1">
<div class="flex-container align-middle align-justify py-2">
<h3 class="sub-block-title text-slate-900 dark:text-slate-25">
{{ title }}
</h3>
<woot-button
variant="clear"
size="tiny"
color-scheme="secondary"
icon="dismiss"
@click="onClose"
/>
</div>
<div class="relative">
<div class="absolute left-0 w-8 h-8 flex justify-center items-center">
<fluent-icon icon="search" class="" size="16" />
</div>
<input
type="text"
:placeholder="$t('HELP_CENTER.ARTICLE_SEARCH.PLACEHOLDER')"
class="block w-full pl-8 h-8 text-sm dark:bg-slate-700 bg-slate-25 rounded-md leading-8 py-1 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-2 focus:ring-woot-100 mb-0 focus:bg-slate-25 dark:focus:bg-slate-700"
:value="searchQuery"
@input="onInput"
/>
</div>
</div>
</template>
<script>
export default {
name: 'ChatwootSearch',
props: {
title: {
type: String,
default: 'Chatwoot',
},
},
data() {
return {
searchQuery: '',
};
},
methods: {
onInput(e) {
this.$emit('search', e.target.value);
},
onClose() {
this.$emit('close');
},
},
};
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div class="flex justify-end gap-1 py-4 bg-white dark:bg-slate-900">
<div class="flex flex-col gap-1 w-full">
<div v-if="isLoading" class="empty-state-message">
<spinner />
{{ $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.SEARCH_LOADER') }}
</div>
<div v-else-if="showNoResults" class="empty-state-message">
{{ $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.NO_RESULT') }}
</div>
<search-result-item
v-for="article in articles"
v-else
:id="article.id"
:key="article.id"
:title="article.title"
:body="article.content"
:url="article.url"
:category="article.category.name"
:locale="article.category.locale"
@preview="handlePreview"
@insert="handleInsert"
/>
</div>
</div>
</template>
<script>
import Spinner from 'shared/components/Spinner.vue';
import SearchResultItem from 'dashboard/routes/dashboard/helpcenter/components/ArticleSearch/ArticleSearchResultItem.vue';
export default {
name: 'SearchResults',
components: {
Spinner,
SearchResultItem,
},
props: {
articles: {
type: Array,
default: () => [],
},
isLoading: {
type: Boolean,
default: false,
},
searchQuery: {
type: String,
default: '',
},
portalSlug: {
type: String,
required: true,
},
},
computed: {
showNoResults() {
return this.searchQuery && this.articles.length === 0;
},
},
methods: {
handlePreview(id) {
this.$emit('preview', id);
},
handleInsert(id) {
this.$emit('insert', id);
},
},
};
</script>
<style scoped>
.empty-state-message {
@apply text-center flex items-center justify-center px-4 py-8 text-slate-500 text-sm;
}
</style>

View File

@@ -0,0 +1,33 @@
import ArticleSearchArticleView from '../ArticleSearch/ArticleView';
export default {
title: 'Components/Help Center/ArticleSearch/ArticleView',
component: ArticleSearchArticleView,
argTypes: {
title: {
defaultValue: 'Insert article',
control: {
type: 'text',
},
},
selectedValue: {
defaultValue: 'Status',
control: {
type: 'text',
},
},
},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { ArticleSearchArticleView },
template:
'<article-search-article-view v-bind="$props"></article-search-article-view>',
});
export const ArticleHeaderView = Template.bind({});
ArticleHeaderView.args = {
title: 'Insert article',
url: 'https://staging.chatwoot.com/hc/home-rental-app/articles/filter-your-housing-search-results-0',
};

View File

@@ -0,0 +1,31 @@
import ArticleSearchHeader from '../ArticleSearch/Header';
export default {
title: 'Components/Help Center/ArticleSearch/Header',
component: ArticleSearchHeader,
argTypes: {
title: {
defaultValue: 'Insert article',
control: {
type: 'text',
},
},
selectedValue: {
defaultValue: 'Status',
control: {
type: 'text',
},
},
},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { ArticleSearchHeader },
template: '<article-search-header v-bind="$props"></article-search-header>',
});
export const ArticleHeaderView = Template.bind({});
ArticleHeaderView.args = {
title: 'Insert article',
};

View File

@@ -1,4 +1,4 @@
import ArticleSearchResultItem from '../ArticleSearchResultItem.vue';
import ArticleSearchResultItem from '../ArticleSearch/ArticleSearchResultItem.vue';
export default {
title: 'Components/Help Center/ArticleSearchResultItem',

View File

@@ -1,26 +1,26 @@
<template>
<div class="p-4 space-y-6 bg-white">
<div class="space-y-2 animate-pulse">
<div class="h-6 bg-slate-100 rounded w-1/5" />
<div class="h-10 bg-slate-100 rounded w-3/5" />
<div class="p-4 space-y-6 bg-white dark:bg-slate-900">
<div class="space-y-2 animate-loader-pulse">
<div class="h-6 bg-slate-100 dark:bg-slate-700 rounded w-1/5" />
<div class="h-10 bg-slate-100 dark:bg-slate-700 rounded w-3/5" />
</div>
<div class="space-y-2 animate-pulse">
<div class="h-5 bg-slate-100 rounded" />
<div class="h-5 bg-slate-100 rounded" />
<div class="h-5 bg-slate-100 rounded" />
<div class="h-5 bg-slate-100 rounded w-4/5" />
<div class="space-y-2 animate-loader-pulse">
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded w-4/5" />
</div>
<div class="space-y-2 animate-pulse">
<div class="h-5 bg-slate-100 rounded" />
<div class="h-5 bg-slate-100 rounded" />
<div class="h-5 bg-slate-100 rounded w-3/5" />
<div class="h-5 bg-slate-100 rounded" />
<div class="space-y-2 animate-loader-pulse">
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded w-3/5" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
</div>
<div class="space-y-2 animate-pulse">
<div class="h-5 bg-slate-100 rounded" />
<div class="h-5 bg-slate-100 rounded" />
<div class="h-5 bg-slate-100 rounded" />
<div class="h-5 bg-slate-100 rounded w-3/5" />
<div class="space-y-2 animate-loader-pulse">
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded w-3/5" />
</div>
</div>
</template>

View File

@@ -138,10 +138,16 @@ module.exports = {
'90%': { transform: 'translateX(-0.375rem)' },
'100%': { transform: 'translateX(0)' },
},
'loader-pulse': {
'0%': { opacity: 0.4 },
'50%': { opacity: 1 },
'100%': { opacity: 0.4 },
},
},
animation: {
...defaultTheme.animation,
wiggle: 'wiggle 0.5s ease-in-out',
'loader-pulse': 'loader-pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite',
},
},
plugins: [