Kateb Tech Docs
Packages

@katebtech/media

Reusable media components and utilities for images, uploads, galleries, and asset handling.

@katebtech/media

Shared media helpers, Cloudinary utilities, and React/Next.js components for Kateb Tech apps.

Purpose

@katebtech/media provides reusable media primitives for Kateb Tech applications. Use it when an app needs consistent image metadata, optimized Cloudinary image URLs, upload and delete helpers, preview galleries, or Quill-based rich text editing.

This package expects React and Next.js from the consuming app.

Package Overview

The package includes:

  • Cloudinary URL builders for common image sizes and layouts.
  • Client upload and delete helpers for Cloudinary assets.
  • Server route factories for signed uploads and protected asset deletion.
  • Image components for previews, galleries, placeholders, and clickable large images.
  • Quill editor components and HTML utilities for rich text content.
  • Public TypeScript types for exported components and shared media data.

File Structure

src
├── cloudinary
│   ├── _lib
│   ├── client
│   └── server
├── images
│   ├── clickable
│   └── clientComponents
└── quil-editor
    ├── _lib
    ├── QuillContentSection.tsx
    ├── QuillEditor.tsx
    └── index.ts

Import Style

Consume package APIs through specific package subpaths.

import { cldGalleryImage } from "@katebtech/media/cloudinary/_lib";
import { CldFileUpload } from "@katebtech/media/cloudinary/client";
import { createCldUploadRoute } from "@katebtech/media/cloudinary/server";
import { ImageGallery, type ImageMeta } from "@katebtech/media/images";
import { QuillEditor } from "@katebtech/media/quill-editor";

Avoid importing from implementation files that are not package exports.

Package Rules

  • Keep Cloudinary client-only components in cloudinary/client.
  • Keep Cloudinary server-only helpers and route factories in cloudinary/server.
  • Keep shared Cloudinary types and URL builders in cloudinary/_lib.
  • Store reusable image data as ImageMeta.
  • Use safeFolder for destructive Cloudinary server actions.
  • Prefer exported subpaths over root imports when consuming this package.
  • Keep package README content focused on package APIs and architecture.

Module Documentation

Cloudinary URL Helpers

Use URL helpers from @katebtech/media/cloudinary/_lib to build optimized Cloudinary image URLs.

import {
  cldGalleryImage,
  cldHeroBackgroundImage,
  cldLargePreviewImage,
  cldServiceCardImage,
  cldThumbnailImage,
} from "@katebtech/media/cloudinary/_lib";

const heroUrl = cldHeroBackgroundImage("services/web-design/hero.jpg");
const cardUrl = cldServiceCardImage("services/web-design/card.jpg");
const galleryUrl = cldGalleryImage("services/web-design/gallery-1.jpg");
const thumbnailUrl = cldThumbnailImage("services/web-design/gallery-1.jpg");
const previewUrl = cldLargePreviewImage("services/web-design/gallery-1.jpg");

Pass a custom Cloudinary cloud name when a consuming app does not use the package default.

import { cldGalleryImage } from "@katebtech/media/cloudinary/_lib";

const imageUrl = cldGalleryImage("folder/image.jpg", {
  cloudName: "my-cloud-name",
});

Cloudinary Client

Use CldFileUpload in client components when a form needs a Cloudinary upload button with preview and remove support.

"use client";

import { CldFileUpload } from "@katebtech/media/cloudinary/client";
import type { CldFileUploadProps } from "@katebtech/media/cloudinary/_lib";
import { useState } from "react";

export function HeroImageUpload() {
  const [imagePath, setImagePath] = useState("");

  const uploadProps: CldFileUploadProps = {
    title: "Hero",
    uploadPreset: "my-upload-preset",
    value: imagePath,
    onChange: setImagePath,
    allowedFormats: ["jpg", "jpeg", "png", "webp"],
    resourceType: "image",
  };

  return <CldFileUpload {...uploadProps} />;
}

value should be the Cloudinary path returned by the upload widget, such as v123/folder/file.png. The component calls onChange(path) after upload and onChange("") after a successful remove.

Use deleteCldAssetClient when deleting an asset from a client-side workflow.

import { deleteCldAssetClient } from "@katebtech/media/cloudinary/client";

const result = await deleteCldAssetClient({
  path: "services/web-design/gallery-1.jpg",
  resourceType: "image",
});

if (!result.ok) {
  console.error(result.message);
}

Cloudinary Server

Use createDeleteCldAssetServer for protected server-side deletion.

import { createDeleteCldAssetServer } from "@katebtech/media/cloudinary/server";

export const deleteCldAssetServer = createDeleteCldAssetServer({
  cloudName: process.env.CLOUDINARY_CLOUD_NAME!,
  apiKey: process.env.CLOUDINARY_API_KEY!,
  apiSecret: process.env.CLOUDINARY_API_SECRET!,
  safeFolder: "services",
});

The server helper only deletes assets whose public id starts with safeFolder, which protects unrelated Cloudinary assets.

Use route factories in Next.js route handlers.

import { createCldUploadRoute } from "@katebtech/media/cloudinary/server";

export const POST = createCldUploadRoute({
  cloudName: process.env.CLOUDINARY_CLOUD_NAME!,
  apiKey: process.env.CLOUDINARY_API_KEY!,
  apiSecret: process.env.CLOUDINARY_API_SECRET!,
});
import { createCldDestroyRoute } from "@katebtech/media/cloudinary/server";

export const POST = createCldDestroyRoute({
  cloudName: process.env.CLOUDINARY_CLOUD_NAME!,
  apiKey: process.env.CLOUDINARY_API_KEY!,
  apiSecret: process.env.CLOUDINARY_API_SECRET!,
  safeFolder: "services",
});

Image Data

Use ImageMeta for reusable image data.

import type { ImageMeta } from "@katebtech/media/images";

const images: readonly ImageMeta[] = [
  {
    url: "services/web-design/gallery-1.jpg",
    alt: "Website homepage preview",
    cld: true,
  },
  {
    url: "/images/local-preview.jpg",
    alt: "Local project preview",
    cld: false,
  },
];

Preview Image

Use PreviewImage for a single clickable image or a lightweight fullscreen preview.

import { PreviewImage } from "@katebtech/media/images";

export function ServiceCard() {
  return (
    <PreviewImage
      image={{
        url: "services/web-design/card.jpg",
        alt: "Web design service preview",
        cld: true,
      }}
      sizes="(min-width: 768px) 33vw, 100vw"
    />
  );
}

Pass multiple images and customize the Cloudinary transform when needed.

import { cldThumbnailImage } from "@katebtech/media/cloudinary/_lib";
import { PreviewImage, type ImageMeta } from "@katebtech/media/images";

const images: ImageMeta[] = [
  {
    url: "services/web-design/gallery-1.jpg",
    alt: "Gallery image",
    cld: true,
  },
];

<PreviewImage
  image={images}
  aspect="aspect-[4/3]"
  imageTransformer={cldThumbnailImage}
/>;

Use ImageGallery for a full gallery with a main image, thumbnails, and fullscreen navigation.

import { ImageGallery, type ImageMeta } from "@katebtech/media/images";

const images: ImageMeta[] = [
  {
    url: "services/web-design/gallery-1.jpg",
    alt: "Homepage screen",
    cld: true,
  },
  {
    url: "services/web-design/gallery-2.jpg",
    alt: "Responsive screen",
    cld: true,
  },
];

export function ProjectGallery() {
  return <ImageGallery images={images} priorityFirstImage />;
}

Clickable Images

Use clickable image components from @katebtech/media/images/clickable when a consuming app needs the larger image preview pattern directly.

import { ClickableLargeImage } from "@katebtech/media/images/clickable";

Placeholders

Use placeholder exports from @katebtech/media/images for shared image fallback UI.

import {
  AVATAR_PLACEHOLDER_DATA_URL,
  AVATAR_PLACEHOLDER_SVG,
  getBlurDataURL,
  HAZARISTAN_FLAG_DATA_URL,
  HAZARISTAN_FLAG_SVG,
  IMAGE_DEFAULT_BLUR,
} from "@katebtech/media/images";

const blurDataURL = getBlurDataURL("#f8fafc");

Quill Editor

Use QuillEditor in client components for a controlled rich text editor with the package toolbar and optional character limit.

"use client";

import {
  QuillEditor,
  type QuillEditorProps,
} from "@katebtech/media/quill-editor";
import { useState } from "react";

export function ServiceDescriptionEditor() {
  const [description, setDescription] = useState("");

  const editorProps: QuillEditorProps = {
    id: "service-description",
    value: description,
    onChange: setDescription,
    placeholder: "Write the service description...",
    maxChars: 1200,
  };

  return <QuillEditor {...editorProps} />;
}

Use QuillContentSection to render saved Quill HTML with the package content styling and cleanup utilities.

import { QuillContentSection } from "@katebtech/media/quill-editor";

export function ServiceDescription({ content }: { content: string }) {
  return <QuillContentSection content={content} />;
}

On this page