npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@turnix-co/konva-editor

v4.0.2

Published

Turnix Konva Editor - A powerful multi-slide canvas editor with drawing, media, and recording capabilities

Downloads

488

Readme

@turnix-co/konva-editor

A powerful, private multi-slide canvas editor built with React, Konva, and Redux. Features drawing tools, media support, recording capabilities, and more.

🔒 Private Package

This package is private and only available to authorized Turnix team members via GitHub Packages.

📦 Installation

For Team Members

Step 1: Install the package from GitHub Packages:

npm install @turnix-co/konva-editor --registry=https://npm.pkg.github.com --legacy-peer-deps

Step 2: Install peer dependencies:

npm install konva react-konva react-konva-utils

Why separate? Konva has platform-specific code (browser vs Node.js). Installing it separately ensures Next.js correctly resolves the browser version and avoids SSR errors.

Setup Authentication

Contact your team lead for authentication credentials (.npmrc file).

⚠️ CRITICAL SETUP REQUIREMENTS

Before you start, understand these requirements or features WON'T work:

  1. Layout Container Required: Components MUST be wrapped in a div with:

    • width: '100vw' and height: '100vh'
    • display: 'flex' and flexDirection: 'column'
    • Canvas wrapper needs flex: 1 and position: 'relative'
  2. stageRef Required: For video recording, you MUST:

    • Create a stageRef with useRef<Konva.Stage | null>(null)
    • Pass it to both <Toolbar stageRef={stageRef} /> and <Canvas onStageReady={...} />
  3. CSS Import Required: Import the package's CSS in your root layout

Common Mistakes (these will break video recording):

// ❌ WRONG - No layout wrapper, no stageRef
<ReduxProvider store={store}>
  <Toolbar />
  <Canvas />
  <SlideNavigation />
</ReduxProvider>

Correct Setup (see Quick Start below)

🚀 Quick Start

1. Import the CSS styles

IMPORTANT: You must import the package's CSS file for components to display properly.

In your root layout or main CSS file (e.g., app/layout.tsx or app/globals.css):

// In your layout.tsx or _app.tsx
import '@turnix-co/konva-editor/styles.css';

Or in your CSS file:

@import '@turnix-co/konva-editor/styles.css';

2. Create your editor page with auto-save

IMPORTANT: To enable IndexedDB persistence (auto-save), you must call the useSlidesPersistence() hook.

CRITICAL FOR VIDEO RECORDING: You MUST wrap components in a properly styled container for video recording to work correctly!

'use client';

import { useRef } from 'react';
import {
  Canvas,
  Toolbar,
  SlideNavigation,
  ReduxProvider,
  store,
  useSlidesPersistence
} from '@turnix-co/konva-editor';
import '@turnix-co/konva-editor/styles.css';
import type Konva from 'konva';

// Wrapper component to load persisted slides
function PersistenceLoader({ children }: { children: React.ReactNode }) {
  useSlidesPersistence(); // ← This loads slides from IndexedDB on mount
  return <>{children}</>;
}

export default function EditorPage() {
  const stageRef = useRef<Konva.Stage | null>(null);

  return (
    <ReduxProvider store={store}>
      <PersistenceLoader>
        {/* CRITICAL: This wrapper is REQUIRED for video recording to work */}
        <div style={{
          width: '100vw',
          height: '100vh',
          display: 'flex',
          flexDirection: 'column',
          overflow: 'hidden' // Prevents scrollbars
        }}>
          <Toolbar stageRef={stageRef} />

          {/* CRITICAL: Canvas wrapper needs flex: 1 and position: relative */}
          <div style={{ flex: 1, position: 'relative' }}>
            <Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
          </div>

          <SlideNavigation />
        </div>
      </PersistenceLoader>
    </ReduxProvider>
  );
}

Important:

  • Always add 'use client' at the top!
  • Call useSlidesPersistence() inside ReduxProvider to enable auto-save
  • MUST have the wrapper div with width: '100vw' and height: '100vh'
  • MUST pass stageRef to both Toolbar and Canvas for recording to work

3. Add Publishing (Optional)

The package includes automatic data persistence via IndexedDB + Redux:

  • Auto-save: Slides are automatically saved to browser storage on every change
  • Auto-restore: Slides are automatically loaded when the page refreshes
  • No backend needed: Data persists in the user's browser

How it works:

  1. useSlidesPersistence() hook loads slides from IndexedDB on mount
  2. Redux middleware automatically saves changes to IndexedDB (500ms debounce)
  3. Works completely offline - no server required!

If you want to publish slides to your backend:

'use client';

import { useRef } from 'react';
import {
  Canvas,
  Toolbar,
  SlideNavigation,
  PublishButton,
  ReduxProvider,
  store,
  useSlidesPersistence,
  type PublishProgress,
  type PublishResponse,
  type Slide
} from '@turnix-co/konva-editor';
import '@turnix-co/konva-editor/styles.css';
import type Konva from 'konva';

// Wrapper component for persistence
function PersistenceLoader({ children }: { children: React.ReactNode }) {
  useSlidesPersistence();
  return <>{children}</>;
}

export default function EditorPage() {
  const stageRef = useRef<Konva.Stage | null>(null);

  // Your custom publish logic
  const handlePublish = async (
    slides: Slide[],
    onProgress?: (progress: PublishProgress) => void
  ): Promise<PublishResponse> => {
    // Update progress
    onProgress?.({
      current: 0,
      total: slides.length,
      status: 'Preparing slides...'
    });

    // Call your API endpoint
    const response = await fetch('/api/publish', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ slides })
    });

    const data = await response.json();

    onProgress?.({
      current: slides.length,
      total: slides.length,
      status: 'Published successfully!'
    });

    return {
      success: true,
      message: 'Slides published successfully!',
      projectId: data.id,
      url: data.url
    };
  };

  return (
    <ReduxProvider store={store}>
      <PersistenceLoader>
        {/* CRITICAL: This wrapper is REQUIRED */}
        <div style={{
          width: '100vw',
          height: '100vh',
          display: 'flex',
          flexDirection: 'column',
          overflow: 'hidden'
        }}>
          <Toolbar stageRef={stageRef} />

          <div style={{ flex: 1, position: 'relative' }}>
            <Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
          </div>

          <SlideNavigation />

          {/* Add PublishButton with your custom publish function */}
          <PublishButton
            onPublish={handlePublish}
            label="Save to Cloud"
          />
        </div>
      </PersistenceLoader>
    </ReduxProvider>
  );
}

📚 Components

Core Components

  • <Canvas /> - Main canvas component with drawing, images, videos, and text
  • <Toolbar /> - Toolbar with drawing tools, colors, and actions
  • <SlideNavigation /> - Multi-slide navigation and management
  • <ScreenRecorder /> - Screen recording functionality
  • <PublishButton /> - Customizable publish button with progress tracking

Redux Setup

  • ReduxProvider - Redux provider (from react-redux)
  • store - Pre-configured Redux store

🎥 Using the Recording Feature

The Toolbar component includes a built-in recording button. To enable it, you need to pass the stage reference from the Canvas component to the Toolbar:

'use client';

import { useState, useRef } from 'react';
import {
  Canvas,
  Toolbar,
  SlideNavigation,
  ReduxProvider,
  store,
  type CanvasProps,
  type ToolbarProps
} from '@turnix-co/konva-editor';
import Konva from 'konva';

export default function EditorPage() {
  const stageRef = useRef<Konva.Stage | null>(null);

  return (
    <ReduxProvider store={store}>
      <div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
        {/* Pass stageRef to Toolbar to enable recording */}
        <Toolbar stageRef={stageRef} />

        <div style={{ flex: 1, position: 'relative' }}>
          {/* Canvas provides the stageRef via onStageReady callback */}
          <Canvas
            onStageReady={(ref) => {
              stageRef.current = ref.current;
            }}
          />
        </div>

        <SlideNavigation />
      </div>
    </ReduxProvider>
  );
}

Key points:

  1. Create a stageRef using useRef<Konva.Stage | null>(null)
  2. Pass the stageRef to the <Toolbar> component
  3. Use the onStageReady callback on <Canvas> to populate the stageRef
  4. IMPORTANT: Wrap Canvas in a container with flex: 1 and position: 'relative'
  5. IMPORTANT: The parent container MUST have width: '100vw' and height: '100vh' for recorded videos to display in full screen
  6. The recording button in the Toolbar will now work correctly

Alternatively, you can handle recording externally:

'use client';

import { useState, useRef } from 'react';
import {
  Canvas,
  Toolbar,
  ScreenRecorder,
  ReduxProvider,
  store
} from '@turnix-co/konva-editor';
import Konva from 'konva';

export default function EditorPage() {
  const stageRef = useRef<Konva.Stage | null>(null);
  const [showRecorder, setShowRecorder] = useState(false);

  const handleRecordingComplete = (videoBlob: Blob, thumbnailDataUrl: string) => {
    // Handle the recorded video
    console.log('Recording completed', videoBlob);
  };

  return (
    <ReduxProvider store={store}>
      <div style={{ width: '100vw', height: '100vh' }}>
        {/* Toolbar with custom recording handler */}
        <Toolbar
          onScreenRecord={() => setShowRecorder(true)}
        />

        <Canvas
          onStageReady={(ref) => {
            stageRef.current = ref.current;
          }}
        />

        {/* External ScreenRecorder component */}
        {showRecorder && (
          <ScreenRecorder
            onClose={() => setShowRecorder(false)}
            stageRef={stageRef}
            onRecordingComplete={handleRecordingComplete}
          />
        )}
      </div>
    </ReduxProvider>
  );
}

🎨 Features

  • ✏️ Drawing tools - Pen and eraser with customizable colors and sizes
  • 🖼️ Image support - Drag & drop, resize, rotate with context menu
  • 🎥 Video support - Upload and playback with thumbnail previews
  • 📝 Text elements - Add and edit text on canvas
  • 🎙️ Audio recording - Record voice notes for individual objects
  • 📹 Screen recording - Record entire canvas with audio
  • 📊 Multi-slide presentations - Create up to 20 slides
  • ↩️ Undo/Redo - Full history management
  • 💾 Auto-save - Automatic persistence with IndexedDB + Redux
  • 📤 Export - Export slides as images
  • 🚀 Publishing - Customizable publish button with progress tracking

🔧 Advanced Usage

Custom Actions

import { useDispatch, addImage, setTool } from '@turnix/konva-editor';

function CustomControls() {
  const dispatch = useDispatch();

  const handleAddImage = () => {
    dispatch(addImage({
      id: 'custom-id',
      src: 'https://example.com/image.jpg',
      x: 100,
      y: 100,
      width: 200,
      height: 200,
    }));
  };

  return <button onClick={handleAddImage}>Add Image</button>;
}

TypeScript Support

Full TypeScript support with exported types:

import type { RootState, ImageElement, CanvasState } from '@turnix/konva-editor';

📖 API Reference

PublishButton Props

interface PublishButtonProps {
  // Custom publish function (required)
  onPublish?: (
    slides: Slide[],
    onProgress?: (progress: PublishProgress) => void
  ) => Promise<PublishResponse>;

  // Custom button label (optional, default: "Publish Slides")
  label?: string;

  // Custom className for positioning/styling (optional)
  className?: string;
}

interface PublishProgress {
  current: number;    // Current progress (e.g., 5)
  total: number;      // Total items (e.g., 10)
  status: string;     // Status message (e.g., "Uploading...")
}

interface PublishResponse {
  success: boolean;   // Whether publish succeeded
  message: string;    // Success/error message
  projectId?: string; // Optional: ID from your backend
  url?: string;       // Optional: URL to published project
}

Example API Endpoint (app/api/publish/route.ts):

import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const { slides } = await request.json();

  // Save to your database
  const project = await db.project.create({
    data: {
      slides: JSON.stringify(slides),
      createdAt: new Date(),
    }
  });

  return NextResponse.json({
    id: project.id,
    url: `/projects/${project.id}`,
  });
}

Redux Actions

Canvas Actions:

  • addImage(payload) - Add image to canvas
  • addVideo(payload) - Add video to canvas
  • addText(payload) - Add text to canvas
  • clearCanvas() - Clear current slide
  • undo() - Undo last action
  • redo() - Redo undone action
  • deleteElement(id) - Delete element by ID

Slide Actions:

  • addSlide() - Create new slide
  • deleteSlide(id) - Delete slide
  • setCurrentSlide(id) - Switch to slide
  • duplicateSlide(id) - Duplicate slide

Toolbar Actions:

  • setTool(tool) - Set active tool ('pen' | 'eraser' | 'text')
  • setPenColor(color) - Set pen color
  • setStrokeWidth(width) - Set stroke width

Utilities

  • exportSlideAsImage(slideId) - Export slide as PNG
  • exportSlideAsPDF(slideId) - Export slide as PDF

🏗️ Architecture

The package uses:

  • React 19 for UI
  • Konva for canvas rendering
  • Redux Toolkit for state management
  • IndexedDB for persistence
  • TypeScript for type safety

🔐 Security

⚠️ This is a private package. Do not:

  • Share authentication credentials
  • Publish package code publicly
  • Include in public repositories

📝 License

UNLICENSED - Private and proprietary software for Turnix team use only.

🆘 Support

For issues or questions, contact the Turnix development team.

🐛 Troubleshooting

Recorded Video Not Displaying Full Screen

If your recorded video doesn't display in full screen when played:

  1. Check Container Styling: Ensure the Canvas is wrapped in a container that fills the viewport:

    <div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
      <Toolbar stageRef={stageRef} />
      <div style={{ flex: 1, position: 'relative' }}>
        <Canvas onStageReady={(ref) => stageRef.current = ref.current} />
      </div>
    </div>
  2. Verify stageRef is Passed: Make sure you're passing the stageRef to the Toolbar component:

    <Toolbar stageRef={stageRef} />
  3. Check onStageReady Callback: Ensure the Canvas component's onStageReady callback is properly setting the stageRef:

    <Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
  4. No Fixed Dimensions: Avoid setting fixed width/height on the Canvas container. Use flex: 1 instead to allow it to fill available space.

Download and Re-record Buttons Not Visible

After recording stops, you should see a preview dialog with three buttons:

  • Add to Canvas - Adds the video to the canvas (clears existing content)
  • Download - Downloads the video file locally
  • Re-record - Discards the recording and starts over

If you don't see these buttons, ensure you're using version 1.3.3 or later:

npm install @turnix-co/konva-editor@latest --registry=https://npm.pkg.github.com

Built with ❤️ by the Turnix team