Skip to content

Commit

Permalink
feat: clarity (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw committed Jun 12, 2024
1 parent a5934d9 commit aab4777
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 3 deletions.
175 changes: 175 additions & 0 deletions docs/content/scripts/marketing/clarity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
title: Clarity
description: Use Clarity in your Nuxt app.
links:
- label: Source
icon: i-simple-icons-github
to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/clarity.ts
size: xs
---

[Clarity](https://clarity.microsoft.com/) by Microsoft is a screen recorder and heatmap tool that helps you understand how users interact with your website.

Nuxt Scripts provides a registry script composable `useScriptClarity` to easily integrate Clarity in your Nuxt app.

### Nuxt Config Setup

The simplest way to load Clarity globally in your Nuxt App is to use Nuxt config. Alternatively you can directly
use the [useScriptClarity](#useScriptClarity) composable.

If you don't plan to send custom events you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) to
disable the script in development.

::code-group

```ts [Always enabled]
export default defineNuxtConfig({
scripts: {
registry: {
clarity: {
id: 'YOUR_ID'
}
}
}
})
```

```ts [Production only]
export default defineNuxtConfig({
$production: {
scripts: {
registry: {
clarity: {
id: 'YOUR_ID',
}
}
}
}
})
```

::

#### With Environment Variables

If you prefer to configure your id using environment variables.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
scripts: {
registry: {
clarity: true,
}
},
// you need to provide a runtime config to access the environment variables
runtimeConfig: {
public: {
scripts: {
clarity: {
id: '', // NUXT_PUBLIC_SCRIPTS_CLARITY_ID
},
},
},
},
})
```

```text [.env]
NUXT_PUBLIC_SCRIPTS_CLARITY_ID=<YOUR_ID>
```

## useScriptClarity

The `useScriptClarity` composable lets you have fine-grain control over when and how Clarity is loaded on your site.

```ts
const { clarity } = useScriptClarity({
id: 'YOUR_ID'
})
// example
clarity("identify", "custom-id", "custom-session-id", "custom-page-id", "friendly-name")
```

Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage.

### ClarityApi

```ts
type ClarityFunctions = ((fn: 'start', options: { content: boolean, cookies: string[], dob: number, expire: number, projectId: string, upload: string }) => void)
& ((fn: 'identify', id: string, session?: string, page?: string, userHint?: string) => Promise<{
id: string
session: string
page: string
userHint: string
}>)
& ((fn: 'consent') => void)
& ((fn: 'set', key: any, value: any) => void)
& ((fn: 'event', value: any) => void)
& ((fn: 'upgrade', upgradeReason: any) => void)
& ((fn: string, ...args: any[]) => void)

export interface ClarityApi {
clarity: ClarityFunctions & {
q: any[]
v: string
}
}

```

### Config Schema

You must provide the options when setting up the script for the first time.

```ts
export const ClarityOptions = object({
/**
* The Clarity token.
*/
id: pipe(string(), minLength(10)),
})
```

## Example

Using Clarity only in production while using `clarity` to send a conversion event.

::code-group

```vue [ConversionButton.vue]
<script setup>
const { clarity } = useScriptClarity()
// noop in development, ssr
// just works in production, client
function sendConversion() {
clarity('event', 'conversion')
}
</script>
<template>
<div>
<button @click="sendConversion">
Send Conversion
</button>
</div>
</template>
```

```ts [nuxt.config.ts Mock development]
import { isDevelopment } from 'std-env'

export default defineNuxtConfig({
scripts: {
registry: {
clarity: isDevelopment
? 'mock' // script won't load unless manually callined load()
: {
id: 'YOUR_ID',
},
},
},
})
```

::
5 changes: 3 additions & 2 deletions docs/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,13 @@ const contributors = useRuntimeConfig().public.contributors
<div class="relative hidden xl:block">
<div class="absolute -z-1 -right-[450px] -top-[200px]">
<div class="w-[450px] grid-transform justify-center items-center grid grid-cols-4 ">
<a v-for="(script, key) in registry.slice(0, 16)" :key="key" ref="card" :href="`/scripts/${script.category}/${script.label.toLowerCase().replace(/ /g, '-')}`" class="card py-5 px-3 rounded block" :style="{ zIndex: key }">
<a v-for="(script, key) in registry.filter(s => s.label !== 'Carbon Ads').slice(0, 16)" :key="key" ref="card" :href="`/scripts/${script.category}/${script.label.toLowerCase().replace(/ /g, '-')}`" class="card py-5 px-3 rounded block" :style="{ zIndex: key }">
<template v-if="typeof script.logo !== 'string'">
<div class="logo h-12 w-auto block dark:hidden" v-html="script.logo.light" />
<div class="logo h-12 w-auto hidden dark:block" v-html="script.logo.dark" />
</template>
<div v-else class="logo h-10 w-auto" v-html="script.logo" />
<div v-else-if="script.logo.startsWith('<svg')" class="logo h-10 w-auto" v-html="script.logo" />
<img v-else class="h-10 w-auto mx-auto logo" :src="script.logo">
</a>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion docs/pages/scripts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ const scriptsCategories = useScriptsRegistry().reduce((acc, script) => {
<div class="logo h-10 w-auto block dark:hidden" v-html="script.logo.light" />
<div class="logo h-10 w-auto hidden dark:block" v-html="script.logo.dark" />
</template>
<div v-else class="logo h-10 w-auto" v-html="script.logo" />
<div v-else-if="script.logo.startsWith('<svg')" class="logo h-10 w-auto" v-html="script.logo" />
<img v-else class="h-10 w-auto mx-auto logo" :src="script.logo">
</div>
<div class="text-gray-500 text-sm font-semibold">
{{ script.label }}
Expand Down
22 changes: 22 additions & 0 deletions playground/pages/third-parties/clarity/nuxt-scripts.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script lang="ts" setup>
import { useHead, useScriptClarity } from '#imports'
useHead({
title: 'Clarity',
})
// composables return the underlying api as a proxy object and a $script with the script state
const { $script } = useScriptClarity({
id: 'mqk2m9dr2v',
})
</script>

<template>
<div>
<ClientOnly>
<div>
status: {{ $script.status }}
</div>
</ClientOnly>
</div>
</template>
13 changes: 13 additions & 0 deletions src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { NpmInput } from './runtime/registry/npm'
import type { PlausibleAnalyticsInput } from './runtime/registry/plausible-analytics'
import type { RegistryScripts } from './runtime/types'
import type { GoogleAdsenseInput } from './runtime/registry/google-adsense'
import type { ClarityInput } from './runtime/registry/clarity'

// avoid nuxt/kit dependency here so we can use in docs

Expand Down Expand Up @@ -139,6 +140,18 @@ export const registry: (resolve?: (s: string) => string) => RegistryScripts = (r
from: resolve('./runtime/registry/hotjar'),
},
},
{
label: 'Clarity',
scriptBundling(options?: ClarityInput) {
return `https://www.clarity.ms/tag/${options?.id}`
},
logo: `https://store-images.s-microsoft.com/image/apps.29332.512b1d3d-80ec-4aec-83bb-411008d2f7cd.76371b6f-9386-463f-bfb0-b75cffb86a4f.bd99f4b1-b18e-4380-aa79-93768763c90d.png`,
category: 'marketing',
import: {
name: 'useScriptClarity',
from: resolve('./runtime/registry/clarity'),
},
},
// payments
{
label: 'Stripe',
Expand Down
73 changes: 73 additions & 0 deletions src/runtime/registry/clarity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useRegistryScript } from '../utils'
import { minLength, object, string, pipe } from '#nuxt-scripts-validator'
import type { RegistryScriptInput } from '#nuxt-scripts'

/**
* <script type="text/javascript">
* (function(c,l,a,r,i,t,y){
* c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
* t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
* y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
* })(window, document, "clarity", "script", "mpy5c6k7xi");
* </script>
*/

type ClarityFunctions = ((fn: 'start', options: { content: boolean, cookies: string[], dob: number, expire: number, projectId: string, upload: string }) => void)
& ((fn: 'identify', id: string, session?: string, page?: string, userHint?: string) => Promise<{
id: string
session: string
page: string
userHint: string
}>)
& ((fn: 'consent') => void)
& ((fn: 'set', key: any, value: any) => void)
& ((fn: 'event', value: any) => void)
& ((fn: 'upgrade', upgradeReason: any) => void)
& ((fn: string, ...args: any[]) => void)

export interface ClarityApi {
clarity: ClarityFunctions & {
q: any[]
v: string
}
}

declare global {
interface Window extends ClarityApi {}
}

export const ClarityOptions = object({
/**
* The Clarity token.
*/
id: pipe(string(), minLength(10)),
})

export type ClarityInput = RegistryScriptInput<typeof ClarityOptions>

export function useScriptClarity<T extends ClarityApi>(
_options?: ClarityInput,
) {
return useRegistryScript<T, typeof ClarityOptions>(
'clarity',
options => ({
scriptInput: {
src: `https://www.clarity.ms/tag/${options.id}`,
},
schema: import.meta.dev ? ClarityOptions : undefined,
scriptOptions: {
use() {
return { clarity: window.clarity }
},
},
clientInit: import.meta.server
? undefined
: () => {
window.clarity = window.clarity || function () {
(window.clarity.q = window.clarity.q || []).push(arguments)

Check failure on line 67 in src/runtime/registry/clarity.ts

View workflow job for this annotation

GitHub Actions / ci

Use the rest parameters instead of 'arguments'
}
},
}),
_options,
)
}

0 comments on commit aab4777

Please sign in to comment.