Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@wagmi/connectors": "^5.1.10",
"@wagmi/core": "^2.13.5",
"bits-ui": "^0.21.16",
"driver.js": "^1.3.6",
"estree-walker": "^3.0.3",
"graphql": "^16.8.2",
"indicatorts": "^2.2.2",
Expand Down Expand Up @@ -109,6 +110,7 @@
"@wagmi/core": "^2.14.1",
"autoprefixer": "^10.4.19",
"bits-ui": "^1.3.4",
"driver.js": "^1.3.6",
"eslint-config-prettier": "^10.1.2",
"eslint-import-resolver-typescript": "^4.3.4",
"eslint-plugin-import": "^2.31.0",
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 115 additions & 0 deletions src/lib/ui/core/InteractiveTour/CustomPopover.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<script lang="ts">
import type { Driver } from 'driver.js'

import { onMount } from 'svelte'

import { cn } from '$ui/utils/index.js'
import { trackEvent } from '$lib/analytics/index.js'

import Button from '../Button/Button.svelte'

type TProps = {
driver: Driver
}

const { driver }: TProps = $props()

const steps = driver.getConfig().steps || []
const state = driver.getState()
const popover = state.activeStep?.popover || {}

function trackWalkthrough(event: string, idx?: number) {
const index = idx ?? state.activeIndex
const step = steps[index]

const stepId =
typeof step?.element === 'string'
? step.element
: step?.element instanceof Element
? step.element.id || undefined
: undefined

trackEvent(`walkthrough_${event}`, {
category: 'Walkthrough',
current_step: index + 1,
total_steps: steps.length,
source_url: window.location.href,
step_id: stepId,
})
}

function handleMoveTo(index: number) {
driver.moveTo(index)

trackWalkthrough('set_step')
}

function handleClose() {
driver.destroy()

trackWalkthrough('close')
}

function handleMovePrev() {
driver.movePrevious()

trackWalkthrough('prev_step')
}

function handleMoveNext() {
driver.moveNext()

trackWalkthrough('next_step')
}

onMount(() => {
if (!state.previousStep) {
trackWalkthrough('start')
}
})
</script>

<div>
<header class="flex max-w-[400px] justify-between">
<h3 class="mb-3 text-lg font-medium">{@html popover.title}</h3>
<Button iconSize="12" size="sm" icon="close" onclick={handleClose}></Button>
</header>

<div class="mb-6">{@html popover.description}</div>

<footer class="flex items-center justify-between">
<div class="flex gap-2">
{#if steps.length > 1}
{#each steps as _, i}
<Button
aria-label="Go to step {i + 1}"
variant="plain"
onclick={() => handleMoveTo(i)}
class={cn(
'h-1.5 w-1.5 rounded-full bg-green-light-3 p-0',
state.activeIndex === i && 'bg-green',
)}
></Button>
{/each}
{/if}
</div>

<div class="flex gap-2">
{#if !driver.isFirstStep()}
<Button variant="border" class="px-4" onclick={handleMovePrev}>Previous</Button>
{/if}

{#if driver.isLastStep()}
<Button variant="fill" onclick={handleClose}>Close</Button>
{:else}
<Button variant="fill" onclick={handleMoveNext}>Next</Button>
{/if}
</div>
</footer>
</div>

<style lang="postcss">
:global(.driver-popover) {
@apply !max-w-[400px] !px-6 !pb-8 !pt-5;
}
</style>
26 changes: 26 additions & 0 deletions src/lib/ui/core/InteractiveTour/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Config } from 'driver.js'

import { mount } from 'svelte'

import CustomPopover from './CustomPopover.svelte'

export async function interactiveTour(initialConfig: Config) {
await import('driver.js/dist/driver.css')

const { driver: driverInstance } = await import('driver.js')

return driverInstance({
...initialConfig,

onPopoverRender: (popover, { config: _, state: _s, driver }) => {
popover.wrapper.innerHTML = ''

mount(CustomPopover, {
target: popover.wrapper,
props: {
driver,
},
})
},
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Meta, StoryObj } from '@storybook/svelte'

import Component from './index.svelte'

const meta = {
component: Component,
parameters: {
layout: 'fullscreen',
},
} satisfies Meta<Component>
type Story = StoryObj<typeof meta>

export default meta

export const InteractiveTour: Story = {}
64 changes: 64 additions & 0 deletions src/stories/Design System - App UI/InteractiveTour/index.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script lang="ts">
import Button from '$ui/core/Button/Button.svelte'
import { interactiveTour } from '$ui/core/InteractiveTour/index.js'

const ids = {
start: 'test-start',
feature1: 'test-feature1',
feature2: 'test-feature2',
feature3: 'test-feature3',
} as const

async function start() {
const id = (id: string) => `#${id}`

const tour = await interactiveTour({
steps: [
{
element: id(ids.start),
popover: {
title: 'New master select',
side: 'bottom',
description: `<p class="mrg-l mrg--b">Paste the concrete smart contract address for fetching relevant metrics for it</p>`,
},
},
{
element: id(ids.feature1),
popover: {
title: 'First Feature',
side: 'bottom',
description: `<p class="mrg-l mrg--b">2) Paste the concrete smart contract address for fetching relevant metrics for it</p>`,
},
},
{
element: id(ids.feature2),
popover: {
title: 'Second Feature',
side: 'bottom',
description: `<p class="mrg-l mrg--b">3) Paste the concrete smart contract address for fetching relevant metrics for it</p>`,
},
},
{
element: id(ids.feature3),
popover: {
title: 'Third Feature',
side: 'bottom',
description: `<p class="mrg-l mrg--b">4) Paste the concrete smart contract address for fetching relevant metrics for it</p>`,
},
},
],
})

tour.drive()
}
</script>

<main class="p-8">
<Button id={ids.start} class="px-6" variant="border" onclick={start}>Test</Button>

<div class="mt-10 flex gap-10 [&>div]:rounded [&>div]:p-3 [&>div]:text-white">
<div class="bg-green" id={ids.feature1}>Feature 1</div>
<div class="bg-blue" id={ids.feature2}>Feature 2</div>
<div class="bg-purple" id={ids.feature3}>Feature 3</div>
</div>
</main>