The Block System
Relevant source files
The following files were used as context for generating this wiki page:
The Block System is the foundational feature of the RPG Character Sheet Builder. It provides a modular, grid-based framework for creating and customizing character sheets. Each element on the sheet—from a title to an input field or a decorative shape—is a "Block." These blocks can be added, removed, resized, and repositioned on a canvas, and their content can be edited directly. The system is designed to be agnostic to any specific RPG ruleset, allowing for maximum flexibility.
The core of the system is a centralized state managed by Zustand, which tracks all blocks and their properties. A dedicated React component, BlockRenderer, is responsible for dynamically rendering each block according to its type and applying styles from a selected theme. User interactions, such as adding a block from the ToolsSidebar or moving it on the Canvas, trigger state updates that are reflected in real-time.
Core Architecture
The system's architecture revolves around three main parts: state management (useSheetStore), the grid layout (Canvas), and the rendering logic (BlockRenderer).
- State Management (
useSheetStore): A Zustand store acts as the single source of truth for the character sheet. It holds an array ofBlockobjects and provides actions to manipulate this array. - UI (
ToolsSidebar): The sidebar provides buttons for users to add new blocks to the canvas. Each button dispatches anaddBlockaction to the store with a specific block type. - Canvas (
Canvas.tsx): This component usesreact-grid-layoutto create a draggable and resizable grid. It reads theblocksarray from the store and maps over it, rendering each block via theBlockRenderer. When a block is moved or resized, the canvas calls theupdateLayoutaction to persist the new coordinates. - Rendering (
BlockRenderer.tsx): This component receives a singleblockobject and uses conditional logic to render the correct HTML and inputs based on the block'stype. It also handles user input, callingupdateBlockDatato save changes back to the store.
The following diagram illustrates the data flow when a user adds a new block to the sheet.
The Block Data Structure
Every item on the canvas is an instance of the Block interface. This object contains all information needed to define a block's position, size, type, content, and configuration.
Sources: src/store/useSheetStore.ts:20-36
The properties of the Block interface are detailed below:
| Property | Type | Description |
|---|---|---|
i | string | A unique ID for the block, required by react-grid-layout. Generated via crypto.randomUUID(). |
x | number | The horizontal position of the block on the grid (in grid units). |
y | number | The vertical position of the block on the grid (in grid units). |
w | number | The width of the block (in grid units). |
h | number | The height of the block (in grid units). |
type | string | A literal type that determines how the block is rendered. E.g., "header", "image", "form-box". |
data | any | An object containing the block's content, such as { text: "..." } or { src: "data:..." }. |
config | object | An object for block-specific configurations, like { level: 1 } for a header or { shapeType: "circle" } for a shape. |
Sources: src/store/useSheetStore.ts:20-36, 88
State Management (useSheetStore)
State is managed globally using a Zustand store, which persists to localStorage under the key rpg-sheet-storage. This allows user creations to be saved automatically.
Sources: src/store/useSheetStore.ts:122
Key Actions
The store exposes several actions to manipulate the blocks array.
| Function | Description |
|---|---|
addBlock(type, config) | Adds a new block of a given type to the blocks array. It's placed at y: Infinity to let react-grid-layout find the next available vertical space. |
removeBlock(id) | Removes a block from the blocks array by filtering by its unique ID i. |
updateBlockData(id, data) | Merges new data into the data object of a specific block. Used for updating text, values, etc. |
updateLayout(layout) | Synchronizes the x, y, w, and h properties of all blocks in the store with the latest state from react-grid-layout. |
setTheme(theme) | Updates the global theme key, which controls the overall visual style of all blocks. |
Sources: src/store/useSheetStore.ts:41-53, 85-121
The following diagram shows the sequence for updating a block's content, such as when a user types into a "form-box".
Block Rendering
The BlockRenderer.tsx component is responsible for translating a Block object into its visual representation. It uses a series of if statements on block.type to render the appropriate JSX.
Sources: src/components/sheet/BlockRenderer.tsx:10
Rendering Flow
This flowchart shows the high-level logic within BlockRenderer.
Theming
The visual appearance of each block is not hardcoded. Instead, BlockRenderer retrieves styling classes from a theme configuration object defined in src/lib/themes.ts. It gets the current theme key from useSheetStore and looks up the corresponding styles.
For example, a header block's class is determined by currentTheme.blocks.header.h1 or currentTheme.blocks.header.h2. This allows the entire sheet's look-and-feel to be changed by simply switching the active theme.
Sources: src/components/sheet/BlockRenderer.tsx:13, 35-39, src/lib/themes.ts:20-23
Block Types
The system supports a variety of block types, each with a specific purpose and configuration. They are added to the canvas via the ToolsSidebar.
Sources: src/store/useSheetStore.ts:26, src/components/layout/ToolsSidebar.tsx:106-133
| Type | Added From | Purpose | data Fields | config Fields |
|---|---|---|---|---|
header | Big Title, Subtitle | Renders a large or medium title. | text: string | level: 1 | 2 |
text | Paragraph | A simple, multi-line text area for notes or descriptions. | text: string | - |
form-box | Input | A labeled text input field, common for stats or attributes. | label: string, value: string | - |
list | List | An unordered list where users can add, edit, and remove items. | items: string[] | - |
image | Image | An area to upload and display an image. | src: string (Data URL) | - |
divider | Divisor | A horizontal line for visually separating sections. | - | - |
shape | Square, Circle | A decorative shape. | - | shapeType: "square" | "circle" | "rounded" | "heart" |
container | Group Container | A background element to visually group other blocks. It has a lower z-index. | - | zIndex: "back" |
Sources: src/store/useSheetStore.ts:25-34, src/components/sheet/BlockRenderer.tsx, src/components/layout/ToolsSidebar.tsx:106-133
Conclusion
The Block System is a modular and extensible architecture that forms the core of the Character Sheet Maker. By abstracting each UI element into a Block object and managing them in a central store, the system allows for dynamic, user-driven layout creation. The separation of data (useSheetStore), rendering (BlockRenderer), and layout (Canvas) makes the system easy to maintain and extend with new block types or themes in the future.