Sone

Core Concepts

Understanding Sone's component system and architecture

Core Concepts

Sone uses a declarative, component-based approach to building layouts. If you're familiar with React, you'll feel right at home with Sone's API.

Components

Sone provides several core components that you can use to build your layouts:

Layout Components

  • Column - A vertical container that stacks children vertically
  • Row - A horizontal container that arranges children side by side
  • TextDefault - A wrapper that applies text styling to all child components

Content Components

  • Text - For displaying text content with rich formatting
  • Span - For styling portions of text within a Text component
  • Photo - For displaying images with various scaling options
  • Path - For drawing custom SVG paths and shapes
  • Table, TableRow, TableCell - For creating structured table layouts

Basic Structure

Every Sone document starts with a root component, typically a Column or Row:

import { Column, Text, sone } from "sone";

function MyDocument() {
  return Column(
    Text("Header").size(24).weight("bold"),
    Text("Body content").size(16)
  ).padding(20);
}

Method Chaining

Sone components use a fluent API with method chaining for styling:

Text("Hello World")
  .size(24)
  .color("blue") 
  .weight("bold")
  .align("center")

This is equivalent to setting multiple properties at once:

Text("Hello World").apply({
  size: 24,
  color: "blue",
  weight: "bold", 
  align: "center"
})

Component Composition

Components can be nested to create complex layouts:

Column(
  // Header section
  Row(
    Photo("logo.png").size(40),
    Text("My App").size(20).weight("bold")
  ).gap(10),
  
  // Content section
  Text("Welcome to my application!")
    .size(16)
    .align("center")
    .padding(20)
).bg("white").padding(30)

Flexbox Layout

Sone uses Yoga Layout (Facebook's Flexbox implementation) for consistent layout behavior:

Row(
  Text("Left").flex(1),   // Takes 1/3 of space
  Text("Center").flex(2), // Takes 2/3 of space
  Text("Right").flex(1)   // Takes 1/3 of space
).justifyContent("space-between")

Styling Properties

All components inherit from a base set of layout properties:

Spacing

  • padding(), paddingTop(), paddingLeft(), etc.
  • margin(), marginTop(), marginLeft(), etc.

Sizing

  • width(), height(), size(width, height)
  • minWidth(), maxWidth(), minHeight(), maxHeight()

Positioning

  • position("absolute" | "relative" | "static")
  • top(), left(), right(), bottom()

Background & Borders

  • bg() for background colors, gradients, or images
  • borderWidth(), borderColor()
  • rounded() for border radius

Visual Effects

  • opacity()
  • shadow() for drop shadows
  • rotate(), scale() for transforms

Rendering & Export

Once you've built your component tree, render it with the sone() function:

const document = Column(
  Text("Hello World").size(24)
).padding(40);

// Export in different formats
const jpgBuffer = await sone(document).jpg();
const pngBuffer = await sone(document).png(); 
const svgBuffer = await sone(document).svg();
const pdfBuffer = await sone(document).pdf();

// Get the canvas directly
const canvas = await sone(document).canvas();

Next.js Integration

For Next.js applications, add this configuration:

// next.config.js
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  serverExternalPackages: ["skia-canvas"],
  webpack: (config, options) => {
    if (options.isServer) {
      config.externals = [
        ...config.externals,
        { "skia-canvas": "commonjs skia-canvas" },
      ];
    }
    return config;
  },
};

export default nextConfig;

Browser Usage

In browsers, you need to provide a renderer implementation. The basic structure is the same, but rendering is handled differently:

import { render, Column, Text } from "sone";

// You'll need to implement a browser renderer
const canvas = await render(
  Column(Text("Hello")), 
  browserRenderer
);

Common Patterns

Card Layout

function Card(title, content) {
  return Column(
    Text(title).size(18).weight("bold"),
    Text(content).size(14).color("gray")
  )
  .padding(20)
  .bg("white")
  .rounded(8)
  .shadow("0 2px 4px rgba(0,0,0,0.1)");
}
function Header() {
  return Row(
    Photo("logo.png").size(40),
    Text("Company Name").size(24).weight("bold")
  )
  .alignItems("center")
  .gap(15)
  .padding(20);
}

Responsive Text

function ResponsiveText(content) {
  return Text(content)
    .autofit(true)  // Automatically adjusts font size to fit
    .maxWidth("100%");
}