Skip to content

Commit 2fdc13a

Browse files
authored
feat: useScriptCrisp and ScriptCrisp (#128)
1 parent 85a9744 commit 2fdc13a

File tree

9 files changed

+587
-1
lines changed

9 files changed

+587
-1
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<script setup lang="ts">
2+
const isLoaded = ref(false)
3+
</script>
4+
5+
<template>
6+
<div class="not-prose">
7+
<div class="flex items-center justify-center p-5">
8+
<ScriptCrisp id="b1021910-7ace-425a-9ef5-07f49e5ce417" class="crisp">
9+
<template #awaitingLoad>
10+
<div class="crisp-icon" />
11+
</template>
12+
<template #loading>
13+
<ScriptLoadingIndicator color="black" />
14+
</template>
15+
</ScriptCrisp>
16+
</div>
17+
<div class="text-center">
18+
<UAlert v-if="!isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Click to load" description="Clicking the button to the right will load the Crisp script" />
19+
<UAlert v-else color="green" variant="soft" title="Crisp is loaded" description="The Crisp Facade component is no longer being displayed." />
20+
</div>
21+
</div>
22+
</template>
23+
24+
<style>
25+
.crisp {
26+
width: 54px;
27+
height: 54px;
28+
border-radius: 54px;
29+
cursor: pointer;
30+
background-color: #1972F5;
31+
position: relative;
32+
bottom: 20px;
33+
right: 24px;
34+
z-index: 100000;
35+
box-shadow: 0 4px 10px 0 rgba(0,0,0!important,.05) !important;
36+
}
37+
.crisp-icon {
38+
position: absolute;
39+
top: 16px;
40+
left: 11px;
41+
width: 32px;
42+
height: 26px;
43+
background-size: contain;
44+
background-repeat: no-repeat;
45+
background-position: center;
46+
background-image: url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjMwIiB3aWR0aD0iMzUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxmaWx0ZXIgaWQ9ImEiIGhlaWdodD0iMTM4LjclIiB3aWR0aD0iMTMxLjQlIiB4PSItMTUuNyUiIHk9Ii0xNS4xJSI+PGZlTW9ycGhvbG9neSBpbj0iU291cmNlQWxwaGEiIG9wZXJhdG9yPSJkaWxhdGUiIHJhZGl1cz0iMSIgcmVzdWx0PSJzaGFkb3dTcHJlYWRPdXRlcjEiLz48ZmVPZmZzZXQgZHk9IjEiIGluPSJzaGFkb3dTcHJlYWRPdXRlcjEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIgc3RkRGV2aWF0aW9uPSIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCBpbj0ic2hhZG93Qmx1ck91dGVyMSIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA3IDAiLz48L2ZpbHRlcj48cGF0aCBpZD0iYiIgZD0iTTE0LjIzIDIwLjQ2bC05LjY1IDEuMUwzIDUuMTIgMzAuMDcgMmwxLjU4IDE2LjQ2LTkuMzcgMS4wNy0zLjUgNS43Mi00LjU1LTQuOHoiLz48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGw9IiNmZmYiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyIiB4bGluazpocmVmPSIjYiIvPjwvZz48L3N2Zz4=)!important
47+
}
48+
@media (max-height: 600px) {
49+
.crisp {
50+
bottom: 14px;
51+
right: 14px;
52+
}
53+
}
54+
</style>
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
---
2+
title: Crisp
3+
description: Show performance-optimized Crisp in your Nuxt app.
4+
links:
5+
- label: useScriptCrisp
6+
icon: i-simple-icons-github
7+
to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/crisp.ts
8+
size: xs
9+
- label: "<ScriptCrisp>"
10+
icon: i-simple-icons-github
11+
to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptCrisp.vue
12+
size: xs
13+
---
14+
15+
[Crisp](https://crisp.chat/) is a customer messaging platform that lets you communicate with your customers through chat, email, and more.
16+
17+
Nuxt Scripts provides a [useScriptCrisp](#usescriptcrisp) composable and a headless Facade Component [useScriptCrisp](#scriptcrisp) component to interact with the Crisp.
18+
19+
## ScriptCrisp
20+
21+
The `ScriptCrisp` component is headless Facade Component wrapping the [useScriptCrisp](#usescriptcrisp) composable, providing a simple, performance optimized way to load Crisp in your Nuxt app.
22+
23+
It's optimized for performance by leveraging the [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers), only loading the Crisp when specific elements events happen.
24+
25+
By default, it will load on the `click` DOM event.
26+
27+
### Demo
28+
29+
::code-group
30+
31+
:crisp-demo{label="Output"}
32+
33+
```vue [Input]
34+
<script setup lang="ts">
35+
const isLoaded = ref(false)
36+
</script>
37+
38+
<template>
39+
<div class="not-prose">
40+
<div class="flex items-center justify-center p-5">
41+
<ScriptCrisp id="b1021910-7ace-425a-9ef5-07f49e5ce417" class="crisp">
42+
<template #awaitingLoad>
43+
<div class="crisp-icon" />
44+
</template>
45+
<template #loading>
46+
<ScriptLoadingIndicator color="black" />
47+
</template>
48+
</ScriptCrisp>
49+
</div>
50+
<div class="text-center">
51+
<UAlert v-if="!isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Click to load" description="Clicking the button to the right will load the Crisp script" />
52+
<UAlert v-else color="green" variant="soft" title="Crisp is loaded" description="The Crisp Facade component is no longer being displayed." />
53+
</div>
54+
</div>
55+
</template>
56+
57+
<style>
58+
.crisp {
59+
width: 54px;
60+
height: 54px;
61+
border-radius: 54px;
62+
cursor: pointer;
63+
background-color: #1972F5;
64+
position: relative; /* change to fixed */
65+
bottom: 20px;
66+
right: 24px;
67+
z-index: 100000;
68+
box-shadow: 0 4px 10px 0 rgba(0,0,0!important,.05) !important;
69+
}
70+
.crisp-icon {
71+
position: absolute;
72+
top: 16px;
73+
left: 11px;
74+
width: 32px;
75+
height: 26px;
76+
background-size: contain;
77+
background-repeat: no-repeat;
78+
background-position: center;
79+
background-image: url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjMwIiB3aWR0aD0iMzUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxmaWx0ZXIgaWQ9ImEiIGhlaWdodD0iMTM4LjclIiB3aWR0aD0iMTMxLjQlIiB4PSItMTUuNyUiIHk9Ii0xNS4xJSI+PGZlTW9ycGhvbG9neSBpbj0iU291cmNlQWxwaGEiIG9wZXJhdG9yPSJkaWxhdGUiIHJhZGl1cz0iMSIgcmVzdWx0PSJzaGFkb3dTcHJlYWRPdXRlcjEiLz48ZmVPZmZzZXQgZHk9IjEiIGluPSJzaGFkb3dTcHJlYWRPdXRlcjEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIgc3RkRGV2aWF0aW9uPSIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCBpbj0ic2hhZG93Qmx1ck91dGVyMSIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA3IDAiLz48L2ZpbHRlcj48cGF0aCBpZD0iYiIgZD0iTTE0LjIzIDIwLjQ2bC05LjY1IDEuMUwzIDUuMTIgMzAuMDcgMmwxLjU4IDE2LjQ2LTkuMzcgMS4wNy0zLjUgNS43Mi00LjU1LTQuOHoiLz48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGw9IiNmZmYiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyIiB4bGluazpocmVmPSIjYiIvPjwvZz48L3N2Zz4=)!important
80+
}
81+
@media (max-height: 600px) {
82+
.crisp {
83+
bottom: 14px;
84+
right: 14px;
85+
}
86+
}
87+
</style>
88+
```
89+
90+
::
91+
92+
### Props
93+
94+
- `trigger`: The trigger event to load the Crisp. Default is `click`. See [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) for more information.
95+
- `id`: The Crisp ID.
96+
- `runtimeConfig`: Extra configuration options. Used to configure the locale. Same as CRISP_RUNTIME_CONFIG.
97+
- `tokenId`: Associated a session, equivalent to using CRISP_TOKEN_ID variable. Same as CRISP_TOKEN_ID.
98+
- `cookieDomain`: Restrict the domain that the Crisp cookie is set on. Same as CRISP_COOKIE_DOMAIN.
99+
- `cookieExpiry`: The cookie expiry in seconds. Same as CRISP_COOKIE_EXPIRATION.
100+
101+
See the [Config Schema](#config-schema) for full details.
102+
103+
### Events
104+
105+
The `ScriptCrisp` component emits a single `ready` event when the Crisp is loaded.
106+
107+
```ts
108+
const emits = defineEmits<{
109+
ready: [crisp: Crisp]
110+
}>()
111+
```
112+
113+
```vue
114+
<script setup lang="ts">
115+
function onReady(crisp) {
116+
console.log('Crisp is ready', crisp)
117+
}
118+
</script>
119+
120+
<template>
121+
<ScriptCrisp @ready="onReady" />
122+
</template>
123+
```
124+
125+
### Crisp API
126+
127+
The component exposes a `crisp` instance that you can access the underlying Crisp API.
128+
129+
```vue
130+
<script setup lang="ts">
131+
const crispEl = ref()
132+
onMounted(() => {
133+
crispEl.value.crisp.do('chat:open')
134+
})
135+
</script>
136+
137+
<template>
138+
<ScriptCrisp ref="crispEl" />
139+
</template>
140+
```
141+
142+
### Slots
143+
144+
The component provides minimal UI by default, only enough to be functional and accessible. There are a number of slots for you to customize the maps however you like.
145+
146+
**default**
147+
148+
The default slot is used to display content that will always be visible.
149+
150+
Tip: It's best to leave this empty as the Crisp replaces this component in its own component.
151+
152+
**awaitingLoad**
153+
154+
The slot is used to display content while the Crisp is loading.
155+
156+
```vue
157+
<template>
158+
<ScriptCrisp>
159+
<template #awaitingLoad>
160+
<div style="width: 54px; height: 54px; border-radius: 54px; cursor: pointer; background-color: #1972F5;">
161+
chat!
162+
</div>
163+
</template>
164+
</ScriptCrisp>
165+
</template>
166+
```
167+
168+
**loading**
169+
170+
The slot is used to display content while the Crisp is loading.
171+
172+
Tip: You should use the `ScriptLoadingIndicator` by default for accessibility and UX.
173+
174+
```vue
175+
<template>
176+
<ScriptCrisp>
177+
<template #loading>
178+
<div class="bg-blue-500 text-white p-5">
179+
Loading...
180+
</div>
181+
</template>
182+
</ScriptCrisp>
183+
</template>
184+
```
185+
186+
## useScriptCrisp
187+
188+
The `useScriptCrisp` composable lets you have fine-grain control over the Crisp SDK. It provides a way to load the Crisp SDK and interact with it programmatically.
189+
190+
```ts
191+
export function useScriptCrisp<T extends CrispApi>(_options?: CrispInput) {}
192+
```
193+
194+
Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage.
195+
196+
### Config Schema
197+
198+
```ts
199+
export const CrispOptions = object({
200+
/**
201+
* The Crisp ID.
202+
*/
203+
id: string(),
204+
/**
205+
* Extra configuration options. Used to configure the locale.
206+
* Same as CRISP_RUNTIME_CONFIG.
207+
* @see https://docs.crisp.chat/guides/chatbox-sdks/web-sdk/language-customization/
208+
*/
209+
runtimeConfig: optional(object({
210+
locale: optional(string()),
211+
})),
212+
/**
213+
* Associated a session, equivalent to using CRISP_TOKEN_ID variable.
214+
* Same as CRISP_TOKEN_ID.
215+
* @see https://docs.crisp.chat/guides/chatbox-sdks/web-sdk/session-continuity/
216+
*/
217+
tokenId: optional(string()),
218+
/**
219+
* Restrict the domain that the Crisp cookie is set on.
220+
* Same as CRISP_COOKIE_DOMAIN.
221+
* @see https://docs.crisp.chat/guides/chatbox-sdks/web-sdk/cookie-policies/
222+
*/
223+
cookieDomain: optional(string()),
224+
/**
225+
* The cookie expiry in seconds.
226+
* Same as CRISP_COOKIE_EXPIRATION.
227+
* @see https://docs.crisp.chat/guides/chatbox-sdks/web-sdk/cookie-policies/#change-cookie-expiration-date
228+
*/
229+
cookieExpiry: optional(number()),
230+
})
231+
```
232+
233+
### CrispApi
234+
235+
```ts
236+
export interface CrispApi {
237+
push: (...args: any[]) => void
238+
is: (name: 'chat:opened' | 'chat:closed' | 'chat:visible' | 'chat:hidden' | 'chat:small' | 'chat:large' | 'session:ongoing' | 'website:available' | 'overlay:opened' | 'overlay:closed' | string) => boolean
239+
set: (name: 'message:text' | 'session:data' | 'session:segments' | 'session:event' | 'user:email' | 'user:phone' | 'user:nickname' | 'user:avatar' | 'user:company' | string, value: any) => void
240+
get: (name: 'chat:unread:count' | 'message:text' | 'session:identifier' | 'session:data' | 'user:email' | 'user:phone' | 'user:nickname' | 'user:avatar' | 'user:company' | string) => any
241+
do: (name: 'chat:open' | 'chat:close' | 'chat:toggle' | 'chat:show' | 'chat:hide' | 'helpdesk:search' | 'helpdesk:article:open' | 'helpdesk:query' | 'overlay:open' | 'overlay:close' | 'message:send' | 'message:show' | 'message:read' | 'message:thread:start' | 'message:thread:end' | 'session:reset' | 'trigger:run' | string, arg2?: any) => any
242+
on: (name: 'session:loaded' | 'chat:initiated' | 'chat:opened' | 'chat:closed' | 'message:sent' | 'message:received' | 'message:compose:sent' | 'message:compose:received' | 'user:email:changed' | 'user:phone:changed' | 'user:nickname:changed' | 'user:avatar:changed' | 'website:availability:changed' | 'helpdesk:queried' | string, callback: (...args: any[]) => any) => void
243+
off: (name: 'session:loaded' | 'chat:initiated' | 'chat:opened' | 'chat:closed' | 'message:sent' | 'message:received' | 'message:compose:sent' | 'message:compose:received' | 'user:email:changed' | 'user:phone:changed' | 'user:nickname:changed' | 'user:avatar:changed' | 'website:availability:changed' | 'helpdesk:queried' | string, callback: (...args: any[]) => any) => void
244+
config: (options: any) => void
245+
help: () => void
246+
[key: string]: any
247+
}
248+
```
249+
250+
For more information, please refer to the [Crisp API documentation](https://docs.crisp.chat/guides/chatbox-sdks/web-sdk/dollar-crisp/).
251+
252+
## Example
253+
254+
Loading the Crisp SDK and interacting with it programmatically.
255+
256+
```vue
257+
<script setup lang="ts">
258+
const crisp = useScriptCrisp({
259+
id: 'YOUR_ID'
260+
})
261+
crisp.set('user:nickname', 'Harlan')
262+
crisp.do('chat:open')
263+
</script>
264+
```

docs/pages/scripts.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const categories = {
2626
label: 'Content',
2727
description: 'Display videos, maps and other content on your website.',
2828
},
29+
support: {
30+
label: 'Support',
31+
description: 'Live chat, help desks and other support tools.',
32+
},
2933
utility: {
3034
label: 'Tools',
3135
description: 'Miscellaneous tools to help you build your website.',
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script lang="ts" setup>
2+
import { useHead } from '#imports'
3+
4+
useHead({
5+
script: [
6+
{
7+
innerHTML: 'window.$crisp=[];window.CRISP_WEBSITE_ID="b1021910-7ace-425a-9ef5-07f49e5ce417";(function(){d=document;s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();',
8+
},
9+
],
10+
})
11+
</script>
12+
13+
<template>
14+
<div>
15+
crisp
16+
</div>
17+
</template>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<template>
2+
<div>
3+
<div>crisp</div>
4+
<ScriptCrisp id="b1021910-7ace-425a-9ef5-07f49e5ce417" class="crisp">
5+
<template #awaitingLoad>
6+
<div class="crisp-icon" />
7+
</template>
8+
<template #loading>
9+
<ScriptLoadingIndicator color="black" />
10+
</template>
11+
</ScriptCrisp>
12+
</div>
13+
</template>
14+
15+
<style>
16+
.crisp {
17+
width: 54px;
18+
height: 54px;
19+
border-radius: 54px;
20+
cursor: pointer;
21+
background-color: #1972F5;
22+
position: fixed;
23+
bottom: 20px;
24+
right: 24px;
25+
z-index: 100000;
26+
box-shadow: 0 4px 10px 0 rgba(0,0,0!important,.05) !important;
27+
}
28+
.crisp-icon {
29+
position: absolute;
30+
top: 16px;
31+
left: 11px;
32+
width: 32px;
33+
height: 26px;
34+
background-size: contain;
35+
background-repeat: no-repeat;
36+
background-position: center;
37+
background-image: url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjMwIiB3aWR0aD0iMzUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxmaWx0ZXIgaWQ9ImEiIGhlaWdodD0iMTM4LjclIiB3aWR0aD0iMTMxLjQlIiB4PSItMTUuNyUiIHk9Ii0xNS4xJSI+PGZlTW9ycGhvbG9neSBpbj0iU291cmNlQWxwaGEiIG9wZXJhdG9yPSJkaWxhdGUiIHJhZGl1cz0iMSIgcmVzdWx0PSJzaGFkb3dTcHJlYWRPdXRlcjEiLz48ZmVPZmZzZXQgZHk9IjEiIGluPSJzaGFkb3dTcHJlYWRPdXRlcjEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIgc3RkRGV2aWF0aW9uPSIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCBpbj0ic2hhZG93Qmx1ck91dGVyMSIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA3IDAiLz48L2ZpbHRlcj48cGF0aCBpZD0iYiIgZD0iTTE0LjIzIDIwLjQ2bC05LjY1IDEuMUwzIDUuMTIgMzAuMDcgMmwxLjU4IDE2LjQ2LTkuMzcgMS4wNy0zLjUgNS43Mi00LjU1LTQuOHoiLz48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGw9IiNmZmYiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyIiB4bGluazpocmVmPSIjYiIvPjwvZz48L3N2Zz4=)!important
38+
}
39+
@media (max-height: 600px) {
40+
.crisp {
41+
bottom: 14px;
42+
right: 14px;
43+
}
44+
}
45+
</style>

0 commit comments

Comments
 (0)