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
40 changes: 40 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"configMigration": true,
"extends": [
"config:recommended",
"group:allNonMajor",
"schedule:weekly",
":disablePeerDependencies",
":maintainLockFilesMonthly",
":semanticCommits",
":semanticCommitTypeAll(chore)"
],
"labels": ["dependencies"],
"rangeStrategy": "bump",
"postUpdateOptions": ["pnpmDedupe"],
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true
},
{
"matchUpdateTypes": ["minor"],
"automerge": false
},
{
"matchUpdateTypes": ["major"],
"automerge": false
}
],
"ignoreDeps": [
"@types/node",
"@types/react",
"@types/react-dom",
"react",
"react-dom",
"typescript",
"tsdown"
]
}

11 changes: 6 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ jobs:
ci:
name: CI
runs-on: ubuntu-latest
strategy:
matrix:
command: ["ci:lint", "ci:test", "build"]

steps:
- name: Checkout Repo
uses: actions/checkout@v4
Expand All @@ -26,8 +30,5 @@ jobs:
- name: Install Dependencies
run: pnpm install

- name: Test
run: pnpm test

- name: Prepack
run: pnpm prepack
- name: Run Command
run: pnpm ${{ matrix.command }}
9 changes: 8 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Update Version
working-directory: packages/event-tracker
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
Expand All @@ -41,8 +42,13 @@ jobs:
git push --set-upstream origin release/${{ github.event.inputs.version }}
git push --tags

- name: Run Tests
working-directory: packages/event-tracker
run: pnpm run ci:test

- name: Build Package
run: pnpm build
working-directory: packages/event-tracker
run: pnpm run build

- name: Create GitHub Release
env:
Expand All @@ -53,6 +59,7 @@ jobs:
--generate-notes

- name: Publish to npm
working-directory: packages/event-tracker
run: pnpm publish --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ node_modules
out/
build
dist

.turbo

# Misc
.DS_Store
Expand Down
2 changes: 1 addition & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pnpm lint-staged && pnpm test
pnpm lint-staged && pnpm ci:test
2 changes: 1 addition & 1 deletion .lintstagedrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"*.{ts,tsx}": ["pnpm lint --fix", "prettier --write --ignore-unknown"]
}
}
112 changes: 88 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,108 @@
<img src='https://github.com/user-attachments/assets/1e417f4e-0f3a-4b56-8f6c-68188572421d' width=340 height=340 />
</p>

# event-tracker &middot; [![MIT License](https://img.shields.io/github/license/offlegacy/event-tracker?color=blue)](https://github.com/offlegacy/event-tracker/blob/main/LICENSE) [![NPM badge](https://img.shields.io/npm/v/@offlegacy/event-tracker?logo=npm)](https://www.npmjs.com/package/@offlegacy/event-tracker)
# event-tracker &middot; [![MIT License](https://img.shields.io/github/license/offlegacy/event-tracker?color=blue)](https://github.com/offlegacy/event-tracker/blob/main/LICENSE) [![NPM Version](https://img.shields.io/npm/v/%40offlegacy%2Fevent-tracker)](https://www.npmjs.com/package/@offlegacy/event-tracker)

Comprehensive solution for event tracking in React applications
> Focus on _what_ to track, not _how_ to track it!

A comprehensive solution for event tracking in React applications. Separate tracking logic from business logic with declarative, type-safe APIs.

## Key Features

- 🎯 Declarative event tracking with type-safe APIs
- 🛡️ Data type validation with schemas
- ⚡️ Optimized performance with event batching
- 🔄 Guaranteed execution order for async operations
- 🔌 Analytics tool agnostic - works with any provider
- 🧩 Clean separation of tracking logic from business logic
- 📦 Lightweight - minimal bundle size impact on your application
## Installation

## Why event-tracker?
To install the `event-tracker` library, you can use npm or yarn. Run one of the following commands in your project directory:

Event tracking is essential for modern web applications, but implementing it cleanly can be challenging. Common pain points include:
Using npm:
```bash
npm install @offlegacy/event-tracker
```

- Mixing tracking logic with business logic
- Managing complex tracking state
- Ensuring reliable event delivery with data type validation
- Maintaining type safety
- Performance overhead
Using yarn:
```bash
yarn add @offlegacy/event-tracker
```

`event-tracker` solves these problems with a declarative API that keeps your code clean and performant.
Using pnpm:
```bash
pnpm install @offlegacy/event-tracker
```

## Official Documentation
## What is Event Tracker?

Visit the [official documentation](https://event-tracker.offlegacy.org/) for detailed information on installation, usage, and more.
Event Tracker is a declarative React library that simplifies the process of implementing complex event tracking, allowing developers to focus more on business logic. It is designed to make event tracking easy and efficient for applications of any scale.

## Contributing
```tsx
import { createTracker } from "@offlegacy/event-tracker";

// Create a tracker instance
const [Track, useTracker] = createTracker({
DOMEvents: {
onClick: (params, context) => {
log("Click event:", params, context);
},
},
});

// Usage in the app
function App() {
return (
<Track.Provider initialContext={{ userId: "userId-123" }}>
<Track.Click params={{ buttonId: "event-click" }}>
<button>Event Click!</button>
</Track.Click>
</Track.Provider>
);
}
```

### Key Features

Event Tracker provides a wide range of features focused on both developer experience and application performance.

| Feature | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Declarative API | Easily declare tracking as components without additional setup or complex code. |
| Analytics Tool Agnostic | Integrate with any tool such as [Google Analytics](https://analytics.google.com/) or [Amplitude](https://amplitude.com/) without modifying existing infrastructure. |
| Clear Separation of Concerns | Keep tracking logic completely separate from business code, improving readability, testability, and maintainability. |
| Optimized Performance | Built-in strategies like batching, debouncing, and throttling minimize network requests, enabling stable tracking without performance degradation. |
| Guaranteed Execution Order | Ensures events are processed in the intended order even in asynchronous scenarios, enabling accurate tracking in complex user flows. |
| Data Validation | Schema validation with [standard-schema](https://github.com/standard-schema/standard-schema) prevents build-time errors and ensures the reliability of collected event data. |

## Core Concepts

To use Event Tracker effectively, there are several key concepts to understand.

### Instance (`createTracker`)

This is the fundamental starting point of the library. Using the `createTracker` function, you create a tracker instance (a collection of `Track` components and the `useTracker` hook). Here, you define event tracking by configuring DOM event handlers, impression handlers, and schemas.

You can create multiple tracker instances for different purposes (for example, one for Google Analytics and another for Amplitude).

We welcome contribution from everyone in the community. Read below for detailed contribution guide.
### Provider (`Track.Provider`)

[CONTRIBUTING.md](https://github.com/offlegacy/event-tracker/blob/main/CONTRIBUTING.md)
Implemented based on React’s Context API. Wrap the top level of your application or a specific component tree with `Track.Provider` to supply common data (context) needed for tracking to child components. For example, passing `userId` or `pageName` as context allows these values to be used during event tracking.

### Event Components (`Track.Click`, `Track.PageView`, etc.)

These are special components that enable declarative event tracking. They are provided as the first element in the array returned by `createTracker`.

- `Track.Click`: Tracks when a click event occurs on child elements.
- `Track.Impression`: Tracks when child elements are displayed on screen.
- `Track.PageView`: Tracks a page view event when the component mounts.

You can use the provided components or create custom ones to handle various user interactions and lifecycle events. Each component accepts `context` and `params` props to pass specific data relevant to the event.

### Custom Hook

Use when you need more complex or conditional event tracking that is not directly tied to component lifecycle or DOM events. The hook allows you to access context from `Track.Provider` and execute defined tracking logic imperatively.

## Visit [official documentation](https://event-tracker.offlegacy.org/)

Visit the [official documentation](https://event-tracker.offlegacy.org/) for detailed information on installation, usage, and more.

## Contributing

### Contributors
We welcome contribution from everyone in the community. Read for detailed [contribution guide](https://github.com/offlegacy/event-tracker/blob/main/CONTRIBUTING.md).

[![contributors](https://contrib.rocks/image?repo=offlegacy/event-tracker)](https://github.com/offlegacy/event-tracker/contributors)

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
41 changes: 41 additions & 0 deletions docs/offlegacy.org/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@offlegacy/offlegacy.org",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"postbuild": "pagefind --site .next/server/app --output-path public/_pagefind",
"clean": "rimraf ./next ./node_modules"
},
"dependencies": {
"@amplitude/analytics-browser": "^2.33.0",
"@offlegacy/event-tracker": "workspace:*",
"@uidotdev/usehooks": "^2.4.1",
"codehike": "^1.0.7",
"lucide-react": "^0.525.0",
"motion": "^12.23.26",
"next": "16.2.6",
"nextra": "^4.6.1",
"nextra-theme-docs": "^4.6.1",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"sonner": "^2.0.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
"@types/node": "^20.19.27",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"eslint-config-next": "15.3.5",
"pagefind": "^1.4.0",
"postcss": "^8.5.6",
"@tailwindcss/postcss": "^4.3.0",
"prettier-plugin-tailwindcss": "^0.6.13",
"tailwindcss": "^4.3.0",
"typescript": "^5.9.3"
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
"@tailwindcss/postcss": {},
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import rootConfig from "../prettier.config.js";
import rootConfig from "../../prettier.config.js";

/** @type {import("prettier").Config} */
const config = {
Expand Down
File renamed without changes
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ const Wrapper = useMDXComponents().wrapper;
export default async function Page(props: { params: Promise<{ mdxPath: string[]; lang: Lang }> }) {
const params = await props.params;
const result = await importPage(params.mdxPath, params.lang);
const { default: MDXContent, toc, metadata } = result;
const { default: MDXContent, metadata, toc, sourceCode } = result;

return (
<>
<Wrapper toc={toc} metadata={metadata}>
<Wrapper metadata={metadata} toc={toc} sourceCode={sourceCode}>
<MDXContent {...props} params={params} />
</Wrapper>
<TrackPageView params={{ title: metadata.title }} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import "nextra-theme-docs/style-prefixed.css";
import "../globals.css";
import { TrackClick, TrackImpression, TrackProvider } from "@/tracker";
import { Logo } from "@/logo";
import type { Lang } from "@/lib/types/lang";
import type { Metadata } from "next";

export const metadata: Metadata = {
Expand Down Expand Up @@ -51,13 +50,7 @@ const footer = (
</TrackImpression>
);

export default async function RootLayout({
params,
children,
}: {
children: React.ReactNode;
params: Promise<{ lang: Lang }>;
}) {
export default async function RootLayout({ params, children }: LayoutProps<"/[lang]">) {
const { lang } = await params;
const pageMap = await getPageMap(lang);
const headersList = await headers();
Expand Down
5 changes: 5 additions & 0 deletions docs/offlegacy.org/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "tailwindcss";

@variant dark (&:where(.dark, .dark *));

@source "./src/**/*.{js,jsx,ts,tsx,md,mdx}";
50 changes: 50 additions & 0 deletions docs/offlegacy.org/src/components/demo-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import { createTracker } from "@offlegacy/event-tracker";
import { toast } from "sonner";
import { motion } from "motion/react";

const [Track] = createTracker({
DOMEvents: {
onClick: (params: { buttonId: string }, context: { userId: string }) => {
toast.success(`${context.userId}: ${params.buttonId}`);
},
},
});

export function DemoButton({ children }: { children: React.ReactNode }) {
return (
<Track.Provider initialContext={{ userId: "demo-user" }}>
<Track.Click params={{ buttonId: "click-me" }} throttle={{ delay: 1000 }}>
<motion.button
type="button"
className="cursor-pointer rounded-lg bg-blue-500 px-6 py-3 font-medium text-white shadow-lg transition-colors hover:bg-blue-600"
initial={{ scale: 0.7, opacity: 0, y: 40 }}
animate={{
scale: [1, 1.08, 1],
opacity: 1,
y: 0,
boxShadow: [
"0 4px 14px 0 rgba(59,130,246,0.25)",
"0 6px 20px 0 rgba(59,130,246,0.40)",
"0 4px 14px 0 rgba(59,130,246,0.25)",
],
}}
transition={{
type: "spring",
stiffness: 400,
damping: 18,
opacity: { duration: 0.4 },
y: { type: "spring", stiffness: 200, damping: 18 },
scale: { repeat: Infinity, repeatType: "loop", duration: 1.8 },
boxShadow: { repeat: Infinity, repeatType: "loop", duration: 1.8 },
}}
whileHover={{ scale: 1.13, boxShadow: "0 8px 32px 0 rgba(59,130,246,0.55)" }}
whileTap={{ scale: 0.96 }}
>
{children}
</motion.button>
</Track.Click>
</Track.Provider>
);
}
Loading