@pagecraft/sdk
v0.0.30
Published
A powerful SDK for building configurable components for the PageCraft editor.
Readme
@pagecraft/sdk
A powerful SDK for building configurable components for the PageCraft editor.
Table of Contents
- @pagecraft/sdk
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:
- Install all project dependencies from the root of the monorepo:
npm install- Build the SDK package to make it available for use:
npm run build:sdkAfter 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:
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/componentsdirectory.
- Generate a new component
Navigate to the components folder:
cd src/lib/componentsRun the following command, replacing [component-example] with your component name in kebab-case:
npx pagecraft g [component-example]This will create two files for your new component:
component-example.tsxcontaining the React componentcomponent-example.module.cssfor the component’s scoped styles
Here is an example of what the generated .tsx file looks like:
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>
);
}- Register the Component
Open src/index.ts and register your new component:
import './global.css';
import { ComponentLibrary } from '@pagecraft/sdk';
import ComponentExample from '@/lib/components/component-example';
export const library: ComponentLibrary = {
name: 'Component Library',
components: {
ComponentExample,
},
};- Build the components library
After making changes, rebuild the component library:
cd [project-root]
npm run build:componentsYour component is now ready to be used within PageCraft.
You can also start the components library in watch mode by running
npm run dev:componentsfrom 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:
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:
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:
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.
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:
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:
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:
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:
- Registers the property with PageCraft, making it editable in the Inspector panel.
- Returns the current value of the property for use in your component.
- 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:
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
export interface ComponentStringProperty extends ComponentPropertyBase {
type: types.Text | types.RichText;
initialValue?: string;
placeholder?: string;
}Example
{
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
export interface ComponentNumberProperty extends ComponentPropertyBase {
type: types.Number | types.Slider;
initialValue?: number;
min?: number;
max?: number;
placeholder?: string;
}Example
{
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
export interface ComponentBooleanProperty extends ComponentPropertyBase {
type: types.Checkbox | types.Switch;
initialValue?: boolean;
}Example
{
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
export interface ComponentEnumProperty<T = string | number | boolean | null> extends ComponentPropertyBase {
type: types.Tabs | types.Select;
options: Array<{ value: T; label: string }>;
initialValue?: T;
placeholder?: string;
}Example
{
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
export interface ComponentIconProperty extends ComponentPropertyBase {
type: types.Icon;
initialValue?: string;
}Example
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
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
{
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:
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:
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:
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
@importstatements to load fonts. Instead, register fonts as shown in the "Adding Fonts" section. - Avoid using fixed positioning for elements. Use
PageCraft.createPortalto 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:
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';