social components inlay-proto.up.railway.app/
atproto components sdui
README.md

@inlay/core#

Build element trees for Inlay, a UI component system for the AT Protocol.

Install#

npm install @inlay/core

Creating elements#

The $ function creates elements. Pass a string NSID as the type:

import { $ } from "@inlay/core";

const el = $("org.atsui.Stack", { gap: "small" },
  $("org.atsui.Text", {}, "Hello"),
  $("org.atsui.Text", {}, "world")
);

You can then turn this tree into JSON with serializeTree(el):

{
  "$": "$",
  "type": "org.atsui.Stack",
  "props": {
    "gap": "small",
    "children": [
      {
        "$": "$",
        "type": "org.atsui.Text",
        "props": { "children": ["Hello"] },
        "key": "0"
      },
      {
        "$": "$",
        "type": "org.atsui.Text",
        "props": { "children": ["world"] },
        "key": "1"
      }
    ]
  }
}

If the components you use are published lexicons, you can use the typed $ overload:

import { $ } from "@inlay/core";
import { Stack, Text } from "./generated/org/atsui";

$(Stack, { gap: "small" },
  $(Text, {}, "Hello"),
  $(Text, {}, "world")
);

Then their props will be checked.

JSX#

Set @inlay/core as the JSX import source in your tsconfig:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@inlay/core"
  }
}

Then use Lexicon modules as tags:

import { jslex } from "@inlay/core";
import * as atsui from "./generated/org/atsui";
const { Stack, Text } = jslex(atsui);

<Stack gap="medium">
  <Text>Hello world</Text>
</Stack>

HTTP imports#

In environments that support URL imports (Val Town, Deno), you can import any published Lexicons like this:

/* @jsxImportSource npm:@inlay/core */
import { Stack, Text } from "https://lex.val.run/org.atsui.*.ts";

<Stack gap="medium">
  <Text>Hello world</Text>
</Stack>

Lexicons#

Components are defined by Lexicons — JSON schemas that declare props. For example, org.atsui.Avatar:

{
  "lexicon": 1,
  "id": "org.atsui.Avatar",
  "defs": {
    "main": {
      "type": "procedure",
      "description": "Circular profile image at a fixed size.",
      "input": {
        "encoding": "application/json",
        "schema": {
          "type": "object",
          "required": ["src"],
          "properties": {
            "src": {
              "type": "unknown",
              "description": "Blob ref for the image."
            },
            "did": {
              "type": "string",
              "format": "did",
              "description": "DID of the blob owner. Used to resolve blob URLs."
            },
            "size": {
              "type": "string",
              "maxLength": 32,
              "description": "Size token.",
              "knownValues": ["xsmall", "small", "medium", "large"],
              "default": "medium"
            }
          }
        }
      },
      "output": {
        "encoding": "application/json",
        "schema": {
          "type": "ref",
          "ref": "at.inlay.defs#response"
        }
      }
    }
  }
}

Use @atproto/lex to generate TypeScript from Lexicon files. For published lexicons, install from the network:

npm install @atproto/lex
npx lex install org.atsui.Stack org.atsui.Text
npx lex build --out generated

For your own lexicons, put .json files in a lexicons/ directory (e.g. lexicons/com/example/MyComponent.json) and point lex build at them:

npx lex build --lexicons lexicons --out generated

Serialization#

Convert element trees to/from JSON-safe representations:

import { serializeTree, deserializeTree } from "@inlay/core";

const json = serializeTree(elementTree);
const restored = deserializeTree(json);

API#

Function Description
$(type, props?, ...children) Create an element from a Lexicon module or NSID string
jslex(namespace) Cast Lexicon modules for use as JSX components (no-op at runtime)
isValidElement(value) Type guard for Element
serializeTree(tree, visitor?) Convert element tree to JSON-serializable form
deserializeTree(tree, visitor?) Inverse of serializeTree
walkTree(tree, mapObject) Recursive tree walker for plain objects
resolveBindings(tree, resolve) Replace at.inlay.Binding elements using a resolver callback

Types#

  • Element{ type: string, key?: string, props?: Record<string, unknown> }
  • LexiconComponent — Object with a $nsid field
  • Component<M> — A Lexicon module usable as a JSX component
  • ElementVisitor(element: Element) => unknown