├── README.md ├── apps └── website │ ├── components │ ├── onboarding │ │ ├── writing-onboarding-steps.tsx │ │ ├── install-library.tsx │ │ ├── onboarding-data.tsx │ │ ├── thank-you.tsx │ │ ├── onboarding-step-completion.tsx │ │ ├── introduction-step.tsx │ │ ├── give-feedback-step.tsx │ │ ├── onboarding-setup.tsx │ │ ├── onboarding-step.tsx │ │ └── creating-steps.tsx │ ├── subtitle.tsx │ ├── external-link.tsx │ ├── button.tsx │ ├── ui │ │ ├── skeleton.tsx │ │ ├── label.tsx │ │ ├── input.tsx │ │ ├── button.tsx │ │ ├── form.tsx │ │ └── select.tsx │ ├── onboarding-skeleton.tsx │ ├── analytics.tsx │ ├── footer.tsx │ ├── hero.tsx │ ├── demo.tsx │ └── code-block.tsx │ ├── .eslintrc.json │ ├── .env.example │ ├── app │ ├── favicon.ico │ ├── globals.css │ ├── page.tsx │ └── layout.tsx │ ├── postcss.config.js │ ├── lib │ ├── utils.ts │ ├── hooks.ts │ └── analytics.ts │ ├── next.config.mjs │ ├── components.json │ ├── .gitignore │ ├── public │ ├── vercel.svg │ ├── next.svg │ └── onboarding-lib-logo.svg │ ├── tsconfig.json │ ├── tailwind.config.ts │ ├── package.json │ └── README.md ├── pnpm-workspace.yaml ├── docs ├── onboarding-lib-logo.png └── onboarding-lib-banner.png ├── packages └── lib │ ├── tsconfig.json │ ├── vite.config.ts │ ├── eslint.config.js │ ├── src │ ├── hooks.ts │ ├── utils.ts │ ├── types.ts │ └── index.tsx │ ├── package.json │ └── README.md ├── .gitignore ├── turbo.json ├── package.json ├── prettier.config.cjs ├── .github └── workflows │ └── ci.yml └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | packages/lib/README.md -------------------------------------------------------------------------------- /apps/website/components/onboarding/writing-onboarding-steps.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'apps/*' -------------------------------------------------------------------------------- /apps/website/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /apps/website/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_POSTHOG_KEY="phc_...." 2 | NEXT_PUBLIC_POSTHOG_HOST="https://eu.posthog.com" 3 | -------------------------------------------------------------------------------- /apps/website/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/useflytrap/onboarding_lib/HEAD/apps/website/app/favicon.ico -------------------------------------------------------------------------------- /docs/onboarding-lib-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/useflytrap/onboarding_lib/HEAD/docs/onboarding-lib-logo.png -------------------------------------------------------------------------------- /docs/onboarding-lib-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/useflytrap/onboarding_lib/HEAD/docs/onboarding-lib-banner.png -------------------------------------------------------------------------------- /apps/website/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/website/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /apps/website/components/subtitle.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react" 2 | 3 | export function Subtitle({ children, ...props }: { children: ReactNode }) { 4 | return ( 5 |

6 | {children} 7 |

8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /packages/lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "jsx": "react" 10 | }, 11 | "exclude": ["./website"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/lib/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { defineConfig } from "vite" 4 | 5 | export default defineConfig({ 6 | test: { 7 | coverage: { 8 | // @ts-ignore 9 | "100": true, 10 | include: ["src"], 11 | reporter: ["text", "json", "html"], 12 | }, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /apps/website/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --gray1: #fcfcfc; 7 | --gray2: #f9f9f9; 8 | --gray3: #f0f0f0; 9 | --gray4: #e8e8e8; 10 | --gray5: #e0e0e0; 11 | --gray6: #d9d9d9; 12 | --gray7: #cecece; 13 | --gray8: #bbbbbb; 14 | --gray9: #8d8d8d; 15 | --gray10: #838383; 16 | --gray11: #646464; 17 | --gray12: #202020; 18 | } 19 | -------------------------------------------------------------------------------- /apps/website/components/external-link.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | 3 | export function ExternalLink( 4 | props: React.AnchorHTMLAttributes 5 | ) { 6 | return ( 7 | 12 | {props.children} 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /apps/website/components/button.tsx: -------------------------------------------------------------------------------- 1 | export function Button({ children, ...props }: any) { 2 | return ( 3 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/website/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
15 | ) 16 | } 17 | 18 | export { Skeleton } 19 | -------------------------------------------------------------------------------- /apps/website/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: 'https', 7 | hostname: 'avatars.githubusercontent.com', 8 | pathname: '/**' 9 | }, 10 | { 11 | protocol: 'https', 12 | hostname: 'www.useflytrap.com', 13 | pathname: '/**' 14 | }, 15 | ] 16 | } 17 | }; 18 | 19 | export default nextConfig; 20 | -------------------------------------------------------------------------------- /apps/website/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "stone", 10 | "cssVariables": false, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /packages/lib/eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import eslint from '@eslint/js'; 3 | import tseslint from 'typescript-eslint'; 4 | 5 | export default tseslint.config( 6 | { 7 | extends: [eslint.configs.recommended], 8 | files: ["{src/test}/**/*.{js,json,ts}"], 9 | }, 10 | { 11 | extends: [...tseslint.configs.recommended], 12 | rules: { 13 | '@typescript-eslint/no-explicit-any': 'off' 14 | } 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /packages/lib/src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | export function useDebounce(value: T, delay: number) { 4 | const [debouncedValue, setDebouncedValue] = useState(value) 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value) 9 | }, delay) 10 | 11 | return () => clearTimeout(handler) 12 | }, [value, delay]) 13 | 14 | return debouncedValue 15 | } 16 | -------------------------------------------------------------------------------- /apps/website/components/onboarding-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@/components/ui/skeleton" 2 | 3 | export function OnboardingSkeleton() { 4 | return ( 5 |
6 | 7 | 8 | 9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | dist/ 13 | 14 | # misc 15 | .DS_Store 16 | .rollup.cache 17 | *.pem 18 | 19 | # debug 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # local env files 25 | .env*.local 26 | 27 | # typescript 28 | *.tsbuildinfo 29 | next-env.d.ts 30 | 31 | #turbo 32 | .turbo 33 | -------------------------------------------------------------------------------- /apps/website/components/analytics.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import posthog from "posthog-js" 4 | import { PostHogProvider as PHProvider } from "posthog-js/react" 5 | 6 | if (typeof window !== "undefined") { 7 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { 8 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, 9 | }) 10 | } 11 | 12 | export function PosthogProvider({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode 16 | }>) { 17 | return {children} 18 | } 19 | -------------------------------------------------------------------------------- /apps/website/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /apps/website/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | 3 | import { Demo } from "@/components/demo" 4 | import { Footer } from "@/components/footer" 5 | import { Hero } from "@/components/hero" 6 | import { Subtitle } from "@/components/subtitle" 7 | 8 | export default function Home() { 9 | return ( 10 |
11 | 12 |
13 |
14 | Onboarding Demo 15 | 16 |
17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /apps/website/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "pipeline": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": ["dist/**", ".next/**", "!.next/cache/**"] 8 | }, 9 | "dev": { 10 | "dependsOn": ["^build"], 11 | "cache": false, 12 | "persistent": true 13 | }, 14 | "lint": { 15 | "cache": false 16 | }, 17 | "release": { 18 | "dependsOn": ["lint", "test", "build"] 19 | }, 20 | "test": { 21 | "dependsOn": ["build"], 22 | "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /apps/website/components/onboarding/install-library.tsx: -------------------------------------------------------------------------------- 1 | import type { OnboardingStepRenderProps } from "onboarding_lib" 2 | 3 | import { CodeBlock } from "../code-block" 4 | import { onboardingSchema } from "../demo" 5 | import { OnboardingStepContainer } from "./onboarding-step" 6 | 7 | export function InstallLibraryStep( 8 | props: OnboardingStepRenderProps 9 | ) { 10 | return ( 11 | 12 | 16 | $ npm install onboarding_lib 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /apps/website/components/onboarding/onboarding-data.tsx: -------------------------------------------------------------------------------- 1 | import type { OnboardingStepRenderProps } from "onboarding_lib" 2 | 3 | import { CodeBlock } from "@/components/code-block" 4 | import { Subtitle } from "@/components/subtitle" 5 | 6 | import { onboardingSchema } from "../demo" 7 | import { OnboardingStepContainer } from "./onboarding-step" 8 | 9 | export function OnboardingDataStep( 10 | props: OnboardingStepRenderProps 11 | ) { 12 | return ( 13 | 14 | Here is your onboarding data 15 | {JSON.stringify(props.form.getValues(), null, 2)} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "version": "0.0.1", 4 | "description": "A tiny headless onboarding library with form validation, schema validation using Zod and persistance with unstorage.", 5 | "repository": "useflytrap/onboarding_lib", 6 | "author": "Rasmus ", 7 | "license": "MIT", 8 | "scripts": { 9 | "dev": "turbo dev", 10 | "build": "turbo build", 11 | "test": "turbo test", 12 | "lint": "turbo lint", 13 | "release": "turbo release", 14 | "format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache", 15 | "format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache" 16 | }, 17 | "devDependencies": { 18 | "@ianvs/prettier-plugin-sort-imports": "^3.7.2", 19 | "prettier": "^2.8.8", 20 | "prettier-plugin-tailwindcss": "^0.1.13", 21 | "turbo": "^1.12.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/website/lib/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from "react" 2 | 3 | /** 4 | * Custom hook for determining if the component is currently mounted. 5 | * @returns {() => boolean} A function that returns a boolean value indicating whether the component is mounted. 6 | * @see [Documentation](https://usehooks-ts.com/react-hook/use-is-mounted) 7 | * @example 8 | * const isComponentMounted = useIsMounted(); 9 | * // Use isComponentMounted() to check if the component is currently mounted before performing certain actions. 10 | */ 11 | export function useIsMounted(): () => boolean { 12 | const isMounted = useRef(false) 13 | 14 | useEffect(() => { 15 | isMounted.current = true 16 | 17 | return () => { 18 | isMounted.current = false 19 | } 20 | }, []) 21 | 22 | return useCallback(() => isMounted.current, []) 23 | } 24 | -------------------------------------------------------------------------------- /apps/website/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /apps/website/components/footer.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | 3 | export function Footer() { 4 | return ( 5 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /apps/website/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next" 2 | import { Inter } from "next/font/google" 3 | 4 | import "./globals.css" 5 | import { Toaster } from "sonner" 6 | 7 | import { PosthogProvider } from "@/components/analytics" 8 | import { Footer } from "@/components/footer" 9 | 10 | const inter = Inter({ subsets: ["latin"] }) 11 | 12 | export const metadata: Metadata = { 13 | title: "ONBOARDING_LIB", 14 | description: 15 | "ONBOARDING_LIB is a tiny headless onboarding library with form validation, schema validation using Zod and persistance with unstorage.", 16 | } 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: Readonly<{ 21 | children: React.ReactNode 22 | }>) { 23 | return ( 24 | 25 | 26 | 27 | {children} 28 |