Skip to content

Commit

Permalink
refactor(RelatedPrompt): component logic to be reusable
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrianArenal committed Jan 21, 2025
1 parent d2adc0d commit b8e20e7
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export const relatedPromptsEndpointAdapter = endpointAdapterFactory<
RelatedPromptsRequest,
RelatedPromptsResponse
>({
endpoint: from =>
interpolate(`${getBeaconServiceUrl(from)}/relatedprompts/{extraParams.instance}`, from),
requestMapper: relatedPromptsRequestMapper,
endpoint:
'https://api.empathy.co/relatedprompts/mymotivemarketplace?store=Labstore+London&lang=en',
requestMapper: ({ query }) => ({ query }),
responseMapper: relatedPromptsResponseMapper,
defaultRequestOptions: {
id: 'related-prompts',
Expand Down
1 change: 1 addition & 0 deletions packages/x-components/src/directives/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './infinite-scroll';
export * from './typing';
84 changes: 84 additions & 0 deletions packages/x-components/src/directives/typing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { Directive } from 'vue';

export interface TypingOptions {
/**
* The text (plain or html) that will be typed into the target element.
*/
text: string;
/**
* The typing speed in milliseconds per character.
*
* @default 1
*/
speed?: number;
/**
* The attribute of the HTML element where the typed text will be placed.
* If not specified, the text will be set as content (innerHTML).
*
* @example 'placeholder'
*/
targetAttr?: string;
}

interface TypingHTMLElement extends HTMLElement {
__timeoutId?: number;
}

const typingDirective: Directive<TypingHTMLElement, TypingOptions> = {
mounted(el, binding) {
execute(el, binding.value);
},

updated(el, binding) {
if (binding.value.text !== binding.oldValue?.text) {
clearTimeout(el.__timeoutId);
execute(el, binding.value);
}
},

unmounted(el) {
clearTimeout(el.__timeoutId);
}
};

/**
* Execute a typing animation in an HTML element.
*
* @param el - The HTML element where the typing animation will be displayed
* @param options - Options for the behavior of the animation.
*/
function execute(el: TypingHTMLElement, options: TypingOptions) {
const { text, speed = 1, targetAttr = '' } = options;

if (!text) {
console.error('v-typing: "text" is required.');
return;
}

let index = 0;
el.__timeoutId = el.__timeoutId || undefined;

const updateContent = (value: string) => {
if (targetAttr) {
el.setAttribute(targetAttr, value);
} else {
el.innerHTML = value;
}
};

const type = () => {
if (index < text.length) {
updateContent(text.slice(0, index + 1));
index++;
el.__timeoutId = setTimeout(type, speed) as unknown as number;
} else {
updateContent(text);
clearTimeout(el.__timeoutId);
el.__timeoutId = undefined;
}
};

type();
}

export default typingDirective;
6 changes: 6 additions & 0 deletions packages/x-components/src/views/home/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,12 @@
<template #related-prompts-group>
<RelatedPromptsTagList
:button-class="'x-button-lead x-button-circle x-button-ghost x-p-0'"
:related-prompt-class="'x-p-20 x-rounded-xl x-w-[300px] x-gap-8'"
:related-prompt-color-classes="[
'x-bg-amber-300',
'x-bg-amber-400',
'x-bg-amber-500'
]"
class="-x-mb-1 x-mt-24 desktop:x-mt-0 x-p-0"
/>
<QueryPreviewList
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,21 @@
<template>
<div
@click="toggleSuggestion(index)"
@keydown="toggleSuggestion(index)"
class="x-related-prompt__button"
:class="[{ 'x-related-prompt-selected__button': isSelected }]"
<button
class="x-flex x-items-center x-p-8 x-justify-between x-text-start"
role="button"
aria-pressed="true"
tabindex="0"
>
<slot name="related-prompt-button-info">
<div class="x-related-prompt__button-info">
<span
class="x-typewritter-initial"
:class="[{ 'x-typewritter-animation': isPromptVisible }]"
:style="{
animationDelay: `${index * 0.4 + 0.05}s`,
'--suggestion-text-length': relatedPrompt.suggestionText.length
}"
>
{{ relatedPrompt.suggestionText }}
</span>
</div>
<CrossTinyIcon v-if="isSelected" class="x-icon-lg" />
<PlusIcon v-else class="x-icon-lg" />
</slot>
</div>
<slot name="extra-content" />
<span v-typing="{ text: relatedPrompt.suggestionText, speed: 50 }"></span>
<component :is="selected ? 'CrossTinyIcon' : 'PlusIcon'" class="x-icon-lg x-self-start" />
</button>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { RelatedPrompt } from '@empathyco/x-types';
import { relatedPromptsXModule } from '../x-module';
import CrossTinyIcon from '../../../components/icons/cross-tiny.vue';
import PlusIcon from '../../../components/icons/plus.vue';
import { use$x } from '../../../composables/index';
import vTyping from '../../../directives/typing';
/**
* This component shows a suggested related prompt.
Expand All @@ -43,39 +26,22 @@
*/
export default defineComponent({
name: 'RelatedPrompt',
directives: {
typing: vTyping
},
components: {
CrossTinyIcon,
PlusIcon
},
xModule: relatedPromptsXModule.name,
props: {
relatedPrompt: {
type: Object as PropType<RelatedPrompt>,
required: true
},
isPromptVisible: {
selected: {
type: Boolean,
default: false
},
isSelected: {
type: Boolean,
default: false
},
index: {
type: Number,
required: true
}
},
setup() {
const x = use$x();
const toggleSuggestion = (index: number): void => {
x.emit('UserSelectedARelatedPrompt', index);
};
return {
toggleSuggestion
};
}
});
</script>
Loading

0 comments on commit b8e20e7

Please sign in to comment.