npm package: @pagecraft/sdk
Description: A powerful SDK for building configurable components for the PageCraft editor.
Latest version: 0.0.25

# @pagecraft/sdk

A powerful SDK for building configurable components for the PageCraft editor.

## Table of Contents

- [@pagecraft/sdk](#pagecraftsdk)
  - [Table of Contents](#table-of-contents)
  - [Introduction](#introduction)
    - [What the SDK provides](#what-the-sdk-provides)
  - [Installation](#installation)
  - [Folder Structure](#folder-structure)
  - [Getting Started](#getting-started)
  - [Component Settings](#component-settings)
    - [Base Component Settings](#base-component-settings)
    - [Positioning Options](#positioning-options)
      - [1. Flexible Position](#1-flexible-position)
      - [2. Fixed Position](#2-fixed-position)
  - [Configuration Properties](#configuration-properties)
    - [Base Property Settings](#base-property-settings)
    - [Supported Property Types](#supported-property-types)
      - [Text Properties](#text-properties)
      - [Number Properties](#number-properties)
      - [Boolean Properties](#boolean-properties)
      - [Enum Properties](#enum-properties)
    - [Icon Properties](#icon-properties)
      - [Collection Properties](#collection-properties)
  - [Creating Portals](#creating-portals)
  - [Adding Fonts](#adding-fonts)
  - [Best Practices and Restrictions](#best-practices-and-restrictions)
  - [TypeScript Types](#typescript-types)

## Introduction

The **PageCraft SDK** is a standalone library that enables developers to build and register fully customizable components for **PageCraft**. These components become available in the drag-and-drop interface, allowing end users to configure and use them seamlessly within PageCraft pages.

### What the SDK provides

- Tools to create reusable and configurable components tailored to your application needs.
- A command-line interface (CLI) to quickly generate new component templates with predefined structure and configuration support.
- Support for managing component settings such as width, height, resizability, and more.
- Utilities, models, and conventions to help keep your components consistent, maintainable, and easily integrable.

With the PageCraft SDK, developers can efficiently extend the capabilities of the page designer with modular components that align with their design and functionality requirements.

## Installation

PageCraft SDK is currently part of the PageCraft monorepo and does not need to be installed separately. To get started:

1. Install all project dependencies from the root of the monorepo:

```bash
npm install
```

2. Build the SDK package to make it available for use:

```bash
npm run build:sdk
```

After the build is complete, the SDK will be ready for usage within the monorepo. You can then start creating and integrating customizable components for PageCraft.

> ℹ️ Note: At this stage, the SDK is only available within the monorepo. In the future, it may be published as a standalone npm package for external usage.

## Folder Structure

The components library that uses the SDK is a React package built with Vite. The source files are organized as follows:

```md
libs/components/src/
├── assets `Icons, images, and other static assets`
├── lib
│ ├── components `Customizable components built with the SDK`
│ ├── ui `Reusable UI components used by the customizable components`
│ └── utils `Utility functions and helpers used by the components library`
├── global.css `Global styles applied across the components library`
└── index.ts `Entry point exporting the components library`
```

This structure helps keep the project organized by separating customizable components, shared UI elements, and utility code, while managing static assets and global styles in dedicated locations.

## Getting Started

Follow these steps to generate and register your first customizable component using PageCraft SDK:

> **Note:** These instructions assume you are executing commands from the `libs/components` directory.

1. **Generate a new component**

Navigate to the components folder:

```bash
cd src/lib/components
```

Run the following command, replacing `[component-example]` with your component name in kebab-case:

```bash
npx pagecraft g [component-example]
```

This will create two files for your new component:

- `component-example.tsx` containing the React component
- `component-example.module.css` for the component’s scoped styles

Here is an example of what the generated `.tsx` file looks like:

```tsx
import * as PageCraft from '@pagecraft/sdk';
import styles from './component-example.module.css';

export default PageCraft.Component({
  label: 'Component Example',
  position: PageCraft.Position.FLEXIBLE,
  defaultWidth: 8,
  defaultHeight: 20,
})(ComponentExample);

function ComponentExample() {
  const [name, setName] = PageCraft.exposeConfigProperty({
    type: PageCraft.types.Text,
    name: 'name',
    initialValue: 'Component Example',
  });

  return (
    <div className={styles.container}>
      <h2>
        🤖 Component <strong>{name}</strong> created. PageCraft saves your dev-sanity again.
      </h2>
    </div>
  );
}
```

2. Register the Component

Open `src/index.ts` and register your new component:

```typescript
import './global.css';

import { ComponentLibrary } from '@pagecraft/sdk';

import ComponentExample from '@/lib/components/component-example';

export const library: ComponentLibrary = {
  name: 'Component Library',
  components: {
    ComponentExample,
  },
};
```

3. Build the components library

After making changes, rebuild the component library:

```bash
cd [project-root]
npm run build:components
```

Your component is now ready to be used within PageCraft.

> You can also start the components library in **watch mode** by running `npm run dev:components` from the root of the project.

## Component Settings

To make a component compatible with PageCraft and define how it behaves in the page designer, it must be wrapped using the `PageCraft.Component` decorator. This decorator allows you to configure various metadata and layout-related settings.

### Base Component Settings

All components support the following base settings:

```tsx
export interface ComponentSettingsBase {
  label?: string; // Label shown in the PageCraft UI
  imageUrl?: string; // Thumbnail used for the component card, should be placed in the assets folder
  singleton?: boolean; // If true, the component can be added only once per page
  category?: ComponentCategory; // The category this component belongs to
}

export enum ComponentCategory {
  UIElements = 'UI Elements',
  Layout = 'Layout',
  Forms = 'Forms',
  Data = 'Data',
  Navigation = 'Navigation',
  Other = 'Other',
}
```

These settings define how your component appears and behaves in the designer's UI.

### Positioning Options

Components can be positioned in one of two ways: **flexible** or **fixed**.

#### 1. Flexible Position

Flexible components can be dragged and dropped anywhere, resized, and moved freely. Use the following interface for flexible settings:

```tsx
export interface FlexibleComponentSettings extends ComponentSettingsBase {
  position: Position.FLEXIBLE;
  defaultWidth?: number; // Default width in grid columns (max 24)
  defaultHeight?: number; // Default height in 8px rows
  resizable?: {
    horizontal?: boolean;
    vertical?: boolean;
  }; // Controls whether the component can be resized
}
```

> 🧠 The layout system is based on a 24-column grid with 8px tall rows.

Example:

```tsx
import * as PageCraft from '@pagecraft/sdk';

PageCraft.Component({
  position: Position.FLEXIBLE,
  label: 'My Flexible Component',
  imageUrl: 'flexible-thumbnail.png',
  defaultWidth: 8, // 8 columns
  defaultHeight: 10, // 10 rows → 80px height
  resizable: {
    horizontal: true,
    vertical: false,
  },
})(MyComponent);
```

#### 2. Fixed Position

Fixed components are positioned absolutely relative to the viewport and do not follow the grid layout for flexible content. Instead, they can be precisely positioned using grid coordinates or centered on the screen.

```tsx
export interface FixedComponentSettings extends ComponentSettingsBase {
  position: Position.FIXED;
  left?: number; // Distance from the left edge, in grid columns (max 24)
  right?: number; // Distance from the right edge, in grid columns
  top?: number; // Distance from the top, in rows (8px per row)
  bottom?: number; // Distance from the bottom, in rows (8px per row)
  width?: number; // Width in grid columns
  height?: number; // Height in 8px rows
  centerHorizontally?: boolean; // If true, horizontally centers the component in the viewport. Should be used with `width`
  centerVertically?: boolean; // If true, vertically centers the component. Should be used with `height`
}
```

> 🧠 left, right, top, and bottom values follow the same grid system: 24 columns wide and 8px per row vertically.

> 📐 Use centerHorizontally and centerVertically along with width or height respectively to align the component in the center of the screen.

This is ideal for overlay components or elements that must remain in a fixed spot on the screen (like modals or floating buttons).

Example:

```typescript
import * as PageCraft from '@pagecraft/sdk';

PageCraft.Component({
  position: Position.FIXED,
  label: 'My Fixed Component',
  imageUrl: 'fixed-thumbnail.png',
  top: 2, // 2 rows → 16px from top
  left: 6, // 6 columns from left
  width: 6, // 6 columns wide
  height: 8, // 8 rows → 64px tall
})(MyFixedComponent);
```

Or to center the component:

```tsx
import * as PageCraft from '@pagecraft/sdk';

PageCraft.Component({
  position: Position.FIXED,
  label: 'Centered Fixed Component (e.g. a popup modal)',
  imageUrl: 'centered-thumbnail.png',
  width: 10,
  height: 12,
  centerHorizontally: true,
  centerVertically: true,
})(MyCenteredComponent);
```

## Configuration Properties

Configuration properties enable components to expose editable fields that users can customize directly from PageCraft’s **Inspector panel**. These properties can represent anything from text and numbers to booleans, dropdown selections, sliders, and more.

To define and manage individual configuration properties, use the `PageCraft.exposeConfigProperty` hook:

```tsx
import * as PageCraft from '@pagecraft/sdk';

const value = PageCraft.exposeConfigProperty({
  type: PageCraft.types.Text,
  name: 'propName',
  label: 'Property Label',
  initialValue: 'Default Value',
});
```

What it does:

1. Registers the property with PageCraft, making it editable in the Inspector panel.
2. Returns the current value of the property for use in your component.
3. Automatically updates when the user modifies the property in the Inspector panel.

> ⚠️ Each config property must have a unique name within the same component.

> You can use multiple calls to exposeConfigProperty within a single component to register and access multiple configurable properties.

### Base Property Settings

All configuration properties share a common base interface:

```tsx
interface ComponentPropertyBase {
  name: string; // Unique key for the property (must be unique within the component)
  label?: string; // Optional label shown in the Inspector
  description?: string; // Optional help text shown in the Inspector
}
```

Each property type extends this interface with additional settings relevant to its behavior and presentation.

### Supported Property Types

PageCraft supports the following property types for configuration:

| Type           | Description                           |
| -------------- | ------------------------------------- |
| **Text**       | Single-line text input                |
| **TextArea**   | Multi-line text input                 |
| **Icon**       | Icon picker, integrates with iconify  |
| **Color**      | Color picker                          |
| **RichText**   | Rich text editor                      |
| **Number**     | Numeric input                         |
| **Slider**     | Slider input for number ranges        |
| **Checkbox**   | Checkbox toggle (true/false)          |
| **Switch**     | Switch toggle (true/false)            |
| **Tabs**       | Tab-based option selector             |
| **Select**     | Dropdown with predefined options      |
| **Collection** | Repeatable list of grouped properties |

#### Text Properties

**Interface**

```tsx
export interface ComponentStringProperty extends ComponentPropertyBase {
  type: types.Text | types.RichText;
  initialValue?: string;
  placeholder?: string;
}
```

**Example**

```tsx
{
  type: PageCraft.types.Text, // or PageCraft.types.RichText
  name: 'propName',
  label: 'User-friendly Label',
  description: 'Help text for the property',
  initialValue: 'Default text value',
  placeholder: 'Optional placeholder text'
}
```

#### Number Properties

**Interface**

```tsx
export interface ComponentNumberProperty extends ComponentPropertyBase {
  type: types.Number | types.Slider;
  initialValue?: number;
  min?: number;
  max?: number;
  placeholder?: string;
}
```

**Example**

```tsx
{
  type: PageCraft.types.Number, // or PageCraft.types.Slider
  name: 'propName',
  label: 'User-friendly Label',
  description: 'Help text for the property',
  initialValue: 5,
  min: 0, // Optional minimum value
  max: 10, // Optional maximum value
  placeholder: 'Optional placeholder text'
}
```

#### Boolean Properties

**Interface**

```typescript
export interface ComponentBooleanProperty extends ComponentPropertyBase {
  type: types.Checkbox | types.Switch;
  initialValue?: boolean;
}
```

**Example**

```tsx
{
  type: PageCraft.types.Checkbox, // or PageCraft.types.Switch
  name: 'propName',
  label: 'User-friendly Label',
  description: 'Help text for the property',
  initialValue: false
}
```

#### Enum Properties

**Interface**

```typescript
export interface ComponentEnumProperty<T = string | number | boolean> extends ComponentPropertyBase {
  type: types.Tabs | types.Select;
  options: Array<{ value: T; label: string }>;
  initialValue?: T;
  placeholder?: string;
}
```

**Example**

```tsx
{
  type: PageCraft.types.Tabs, // or PageCraft.types.Select
  name: 'propName',
  label: 'User-friendly Label',
  description: 'Help text for the property',
  options: [
    { value: 'option1', label: 'Option 1' },
    { value: 'option2', label: 'Option 2' }
  ],
  initialValue: 'option1',
  placeholder: 'Optional placeholder text'
}
```

### Icon Properties

**Interface**

```typescript
export interface ComponentIconProperty extends ComponentPropertyBase {
  type: types.Icon;
  initialValue?: string;
}
```

**Example**

```tsx
import * as PageCraft from '@pagecraft/sdk';

const icon = PageCraft.exposeConfigProperty({
  type: PageCraft.types.Icon,
  name: 'icon',
  label: 'Icon',
  initialValue: 'mdi:home'
});

...

return (
  <PageCraft.Icon icon={icon} width={24} height={24} noobserver />
)
```

#### Collection Properties

Collections allow you to create complex nested data structures:

**Interface**

```typescript
export interface ComponentCollectionProperty extends ComponentPropertyBase {
  type: types.Collection;
  itemSchema: ComponentCollectionItemSchema;
  initialValue?: Array<ComponentCollectionItemValue>;
}

export interface ComponentCollectionItemSchema {
  props: ReadonlyArray<PrimitiveComponentProperty>;
  viewPropName?: string;
  childItemSchema?: ComponentCollectionItemSchema;
}

export interface ComponentCollectionItemValue {
  value: Record<string, PrimitiveComponentProperty['initialValue']>;
  children?: ComponentCollectionItemValue[];
}

export type PrimitiveComponentProperty =
  | ComponentStringProperty
  | ComponentNumberProperty
  | ComponentBooleanProperty
  | ComponentEnumProperty;
```

**Example**

```tsx
{
  type: PageCraft.types.Collection,
  name: 'items',
  label: 'Collection Items',
  itemSchema: {
    props: [
      {
        type: PageCraft.types.Text,
        name: 'title',
        label: 'Title'
      },
      {
        type: PageCraft.types.Text,
        name: 'description',
        label: 'Description'
      }
    ],
    viewPropName: 'title', // Property to display in the collection editor
    childItemSchema: {
      // Optional nested collection items
      props: [
        {
          type: PageCraft.types.Text,
          name: 'subItem',
          label: 'Sub Item'
        }
      ]
    }
  },
  initialValue: [
    { value: { title: 'Item 1', description: 'Description 1' } },
    { value: { title: 'Item 2', description: 'Description 2' } }
  ]
}
```

Using collections with TypeScript:

```tsx
const items =
  PageCraft.exposeConfigProperty <
  PageCraft.CollectionList<
    {
      title: string;
      description: string;
    },
    PageCraft.CollectionList<{
      subItem: string;
    }>
  >({
    type: PageCraft.types.Collection,
    // other properties...
  });
```

## Creating Portals

In some cases, you may need to render a component outside of the component grid—for example, when building modals, tooltips, or overlays. PageCraft provides a utility method for this: `PageCraft.createPortal`.

Under the hood, this function wraps `ReactDOM.createPortal`, and injects your component into a dedicated portal container within the PageCraft editor.

You can use the createPortal method like so:

```tsx
return PageCraft.createPortal(<div>Your modal or overlay content</div>);
```

This will inject your content into the PageCraft portal container, preserving layering and positioning context.

## Adding Fonts

If your components use external fonts, you need to register those font URLs in the component library declaration to ensure they are loaded properly inside the PageCraft editor and runtime environment.

Fonts are declared via the `fontUrls` field in the library configuration object defined in `src/index.ts`:

```tsx
export const library: ComponentLibrary = {
  name: 'Component Library',
  fontUrls: [
    'https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap',
    'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap',
    ...
  ],
  components: {
    ComponentExample,
    ...
  },
};
```

> The fontUrls array should include fully qualified URLs pointing to your font stylesheets (e.g. from Google Fonts).

## Best Practices and Restrictions

- Use **only functional components** defined with named function declarations; avoid class components and arrow functions.
- Do **not** reuse existing configuration property names within the same component to prevent naming conflicts.
- Do **not** use `@import` statements to load fonts. Instead, register fonts as shown in the ["Adding Fonts"](#adding-fonts) section.
- Avoid using **fixed positioning** for elements. Use `PageCraft.createPortal` to inject overlays or modals instead.
- Avoid installing large third-party dependencies that significantly increase the bundle size.

## TypeScript Types

The SDK provides comprehensive TypeScript type definitions for all APIs:

```tsx
import {
  // Component property types
  types,
  ComponentProperty,
  ComponentStringProperty,
  ComponentNumberProperty,
  ComponentBooleanProperty,
  ComponentEnumProperty,
  ComponentCollectionProperty,
  ComponentCollectionItemSchema,
  ComponentCollectionItemValue,
  PrimitiveComponentProperty,
  CompositeComponentProperty,

  // Component settings types
  Position,
  ComponentCategory,
  FlexibleComponentSettings,
  FixedComponentSettings,
  ComponentSettings,

  // Collection helper types
  CollectionList,
} from '@pagecraft/sdk';
```